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:
- CocoaPods: Embedding framework resources.
- React Native/Flutter: Bundling JavaScript/Dart code and assets.
- Linting: Running SwiftLint or formatters.
- 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).
- Open your project in Xcode.
- Navigate to the Build Settings tab of your primary target.
- Search for "User Script Sandboxing".
- Change the value from
YestoNo.
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 node, yarn, pod, 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.
- Navigate to your project folder:
Pods/Target Support Files/Pods-ProjectName/. - Open
Pods-ProjectName-frameworks.sh. - 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.
- Go to Build Settings.
- Search for Excluded Architectures.
- Under Debug and Release, expand the list.
- Select Any iOS Simulator SDK.
- 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.
- Exit Code 1: General error. Usually a syntax error in the script, or a command (like
cporrm) failed because a file didn't exist. - Exit Code 126: Command invoked cannot execute. usually a permission issue (
chmod +xis missing). - Exit Code 127: Command not found. The script called
flutter,sentry-cli, ornode, 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.