Skip to main content

Fix "Command PhaseScriptExecution failed" in Xcode (2026 Guide)

 There is no workflow interruption quite as jarring as a failing iOS build. You wait for the compiler to churn through thousands of files, only to be stopped dead by the dreaded red stop sign: "Command PhaseScriptExecution failed with a nonzero exit code."

Unlike syntax errors, this error provides almost no immediate context. It doesn't point to a line of Swift or Objective-C code. It simply means a shell script running during your build process crashed.

Whether you are a native iOS engineer or a React Native/Flutter developer, this guide dissects the root causes of this error and provides the specific, verified fixes for Xcode 16+ and Apple Silicon environments.

The Root Cause: What is PhaseScriptExecution?

In Xcode, the "Build Phases" tab controls the assembly line of your application. While some phases are standard (Compile Sources, Link Binary), others are custom shell scripts.

These scripts handle tasks like:

  1. CocoaPods: Embedding framework resources.
  2. React Native/Flutter: Bundling JavaScript/Dart code and assets.
  3. Linting: Running SwiftLint or formatters.
  4. Crashlytics: Uploading dSYM files.

When Xcode reports Command PhaseScriptExecution failed, it means the shell instance executing one of these scripts returned an exit code other than 0.

Because most build scripts begin with set -e, a single warning or minor path error is treated as a fatal exception, halting the entire build process.


Solution 1: The "User Script Sandboxing" Lockout

Starting with Xcode 15 and continuing into 2026, Apple aggressively tightened build security. The most common cause of script failures today is the User Script Sandboxing setting blocking access to files outside the source root.

If your script tries to update a version number, modify a file in DerivedData, or access a global Node module, the sandbox will kill it.

The Fix

You must explicitly disable sandboxing for scripts that require broad filesystem access (common in React Native and Flutter).

  1. Open your project in Xcode.
  2. Navigate to the Build Settings tab of your primary target.
  3. Search for "User Script Sandboxing".
  4. Change the value from Yes to No.

Note: If you cannot disable this for policy reasons, you must define input/output file lists in the Build Phase strictly so Xcode knows exactly what the script touches.


Solution 2: The React Native / Flutter Environment Gap

Xcode build phases execute in a stripped-down shell environment. They do not automatically load your ~/.zshrc or ~/.bash_profile.

If your build script relies on tools like nodeyarnpod, or flutter, the script will fail with "Command not found" because Xcode doesn't know where they are installed (especially if you use Homebrew on Apple Silicon, which uses /opt/homebrew).

The Diagnosis

Check the expanded build log (click the icon with generic lines in the Report Navigator). If you see lines like node: command not found, this is your issue.

The Fix

You must explicitly export the path in the script causing the error, or create a symbiotic link.

Option A: The Build Phase Fix (Recommended) Locate the failing Build Phase (e.g., "Bundle React Native code and images") and prepend the export command to the script block:

# Add this line at the very top of the script
export PATH="$PATH:/opt/homebrew/bin:/usr/local/bin"

# ... rest of the original script ...
if [[ -s "$HOME/.nvm/nvm.sh" ]]; then
  . "$HOME/.nvm/nvm.sh"
elif [[ -x "$(command -v brew)" && -s "$(brew --prefix nvm)/nvm.sh" ]]; then
  . "$(brew --prefix nvm)/nvm.sh"
fi

Option B: The Symlink Fix If you cannot modify the script (e.g., it is locked in a generic node_module), symlink the binary to a location Xcode always checks.

Open your terminal and run:

# For Apple Silicon (M1/M2/M3)
sudo ln -s /opt/homebrew/bin/node /usr/local/bin/node

Solution 3: The CocoaPods Resources Archiving Bug

A notorious issue persists in the helper scripts generated by CocoaPods, specifically [CP] Embed Pods Frameworks. This often manifests only when Archiving for the App Store, not during debug builds.

The script attempts to read symlinks and re-sign frameworks, but recent versions of Xcode handling extended attributes (xattr) cause the script to crash on valid files.

The Code Fix

You need to edit the shell script generated by CocoaPods. While pod install will overwrite this, applying this fix proves the root cause before you patch your Podfile.

  1. Navigate to your project folder: Pods/Target Support Files/Pods-ProjectName/.
  2. Open Pods-ProjectName-frameworks.sh.
  3. Find the section handling readlink.

Replace this block:

source="$(readlink "${source}")"

With this robust check:

if [ -L "${source}" ]; then
  echo "Symlink found: ${source}"
  source="$(readlink "${source}")"
fi

Permanent Fix (Podfile Post-Install Hook): To ensure pod install doesn't break this again, add this post_install hook to your Podfile:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      # Fix the permission issue for Code Signing
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
    end
  end
  
  # Patch the frameworks script automatically
  Dir.glob("Pods/Target Support Files/Pods-*/*.sh").each do |path|
    puts "Patching symlink issue in #{path}"
    text = File.read(path)
    new_contents = text.gsub('source="$(readlink "${source}")"', 'if [ -L "${source}" ]; then source="$(readlink "${source}")"; fi')
    File.open(path, "w") { |file| file.puts new_contents }
  end
end

Solution 4: Architecture Mismatches (M-Series Chips)

If you migrated from an Intel Mac to an M-Series (Apple Silicon) machine, or if you are using a library that includes pre-compiled static binaries, Xcode might be trying to run an x86_64 script on an arm64 slice.

The Diagnosis

Look at the build log. If you see Bad CPU type in executable, your script is trying to run a tool that isn't compatible with your architecture.

The Fix: Excluded Architectures

You must force Xcode to ignore the simulator architecture for specific build configurations.

  1. Go to Build Settings.
  2. Search for Excluded Architectures.
  3. Under Debug and Release, expand the list.
  4. Select Any iOS Simulator SDK.
  5. Add arm64 (if you are trying to force Rosetta/Intel) or check your Podfile configuration.

The Modern Podfile Fix: For React Native/Flutter projects, ensure your Podfile handles the architecture filtering correctly:

# Podfile
post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      # Ensure we are not excluding arm64 for actual devices
      config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "i386"
    end
  end
end

Deep Dive: Decoding the Exit Codes

While the error message is generic, the exit code hidden in the logs tells the real story. Open the Report Navigator (Cmd + 9) and expand the failed step.

  1. Exit Code 1: General error. Usually a syntax error in the script, or a command (like cp or rm) failed because a file didn't exist.
  2. Exit Code 126: Command invoked cannot execute. usually a permission issue (chmod +x is missing).
  3. Exit Code 127: Command not found. The script called fluttersentry-cli, or node, and it isn't in the $PATH. (Refer to Solution 2).

Pro Tip: Locking Dependencies

If this error appeared suddenly after a pod install or npm install, inspect your lock files. A dependency update likely introduced a build script change that isn't compatible with your Xcode version.

Always commit Podfile.lock and package-lock.json / yarn.lock to ensure your CI/CD pipeline uses the exact same script versions as your local machine.

Conclusion

The PhaseScriptExecution failed error is a rite of passage for iOS developers. It is rarely a problem with your application code, but rather a misalignment between Xcode's strict build environment and the dynamic nature of modern development tools.

By disabling User Script Sandboxing, explicitly defining environment paths, and patching legacy CocoaPods scripts, you can resolve 95% of these occurrences. For the remaining 5%, always check the raw build logs for the specific line triggering the non-zero exit code.