xcodebuild exit code 65:
These four words frighten iOS and macOS developers with any CI system experience. Some of you might even have seen it while using Xcode itself but weren’t aware since Xcode hides those error codes from you.
In order to prevent
xcodebuild from just returning that exit code without any kind of error message, we have to understand what exit code 65 actually refers to.
If you type
man xcodebuild into your terminal emulator of choice and scroll to (almost) the bottom, you’ll see this little paragraph just before the usage examples:
This little hint leads us to the
sysexits man page. Like HTTP status codes 200 OK, 404 Not Found or 418 I’m a teapot, common executable return codes are defined in the
sysexits.h header file. A common exit code is exit code 65 or bad data input.
Knowing this, we can examine why
xcodebuild seems to love exiting with code 65.
xcodebuild is undoubtedly a complicated piece of software, essentially
make(1) on steroids. It determines your project or workspace structure and will hand everything to a compiler along with the correct flags. A single command in the logs could range in length from 50 characters to filling an entire Safari window running in fullscreen mode on a 27” iMac.
If something doesn’t go the way
xcodebuild expected (which happens a lot), it will return exit code 65. What’s most frustrating is that
xcodebuild rarely leaves any context for the issue it couldn’t process. We grappled with one of these situations for almost a year.
xcodebuild finishes executing one of its actions (clean, build, test, analyze, …), the next one is immediately executed without any preparation. At that point, fixed timeout counters are started. If your hardware isn’t fast enough, those timeouts hit 0 before the iOS Simulator has the chance to fully boot.
This problem was exacerbated in some setups since the iOS Simulator would boot on a background thread, which would be de-prioritized if there was anything more important happening in another thread. Once the timeout hit 0,
xcodebuild would try and fail to connect to the iOS Simulator several times. This is because
xcodebuild sits between the compiled binary and the Simulator, so it doesn’t know the speed of the underlying hardware and can’t necessarily recover.
On your local machine, you could fix the issue by rolling your eyes, sighing, and hitting CMD+U again. But on CircleCI, our system shut everything down and recycled the VM that your last build ran in.
In your normal development workflow, there is no derived data folder or other files common to Xcode. The working directory won’t exist, and no iOS Simulator will be running. The second a new build is kicked off on CircleCi, we’re faced with the same issue of
xcodebuild potentially being unable to recover from an unknown state and returning exit code 65.
The best way to mitigate this is to boot the iOS Simulator early with the help of the instrument’s command line tool. This will boot the iOS Simulator and immediately return, so
xcodebuild can start compiling your code. We usually recommend doing that separately from the dependency step to remind your future self/colleague that this is a vital build step — as essential as fetching dependencies through Carthage or updating fastlane.
dependencies: pre: - xcrun instruments -w 'iPhone 7 (10.3)' || sleep 15
sleep 15 is important for two reasons: it gives the iOS Simulator some breathing room to fully boot and returns a 0 exit code. When chaining commands together without the use of
set -o pipefail, the last command’s exit code will be returned, and the earlier commands’ codes will be disregarded. CircleCI looks for the build step’s exit code to determine success/failure. Excluding the sleep command would mean that
xcrun instruments would return a non-0 exit code since the
-t (template) flag was not passed.
Hopefully, this explanation into the infamous
xcodebuild exit code 65 gives you some insight into why
xcodebuild does the things it does. Whenever you see that code,
xcodebuild has encountered a non-recoverable issue. We’re continuing to work closely with Apple’s Developer Tools Team to make the developer toolchain faster and more stable.