EngineeringLast Updated Oct 31, 20255 min read

xcodebuild Exit Code 65: What it is and how to solve for iOS and macOS builds

Xcodebuild exit code 65: for iOS and macOS developers with any CI system experience, these are frightening words. Some of you might not even be aware because Xcode hides those error codes from you.

In order to prevent xcodebuild from just returning that exit code without any kind of error message, you have to understand what exit code 65 actually refers to.

If you type man xcodebuild into your terminal emulator and scroll to (almost) the bottom, there’s a short paragraph just before the usage examples.

xcodebuild exit code 65

This little hint leads 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.

man xcodebuild exit code 65

Why does xcodebuild exit with code 65?

Why does xcodebuild seem to love exiting with code 65? While exit code 65 can have multiple causes—from code signing issues to dependency problems—one of the most common in CI environments is simulator boot timeouts.

xcodebuild is a complicated piece of software, and while it’s improved over the years, this complexity remains. 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 before identifying the root cause.

Whenever 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 because the iOS Simulator would boot on a background thread. This background thread 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, each job runs in a fresh environment, so you can’t just retry—you need to ensure that the simulator boots reliably every time.

Xcode in CircleCI workflows

In your normal development workflow, there is no derived data folder or other file 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, you’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 simctl, the simulator 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 CocoaPods, Swift Package Manager, or updating fastlane.

version: 2.1

jobs:
  build-and-test:
    macos:
      xcode: 15.4.0
    resource_class: macos.m1.medium.gen1  # Apple Silicon Macs are faster for builds
    steps:
      - checkout
      - run:
          name: Pre-boot iOS Simulator
          command: xcrun simctl boot "iPhone 15 Pro" || true
      - run:
          name: Install dependencies
          command: bundle install
      - run:
          name: Build and test
          command: |
            xcodebuild test \
              -workspace YourApp.xcworkspace \
              -scheme YourApp \
              -destination 'platform=iOS Simulator,name=iPhone 15 Pro,OS=17.5'

workflows:
  test:
    jobs:
      - build-and-test

Giving the iOS Simulator time to fully boot

The || true is important because simctl boot will return a non-zero exit code if the simulator is already booted, and you don’t want that to fail the build. CircleCI looks for the build step’s exit code to determine success or failure. By placing the boot command early, the simulator boots in the background while your dependency installation runs, giving it time to fully initialize before xcodebuild needs it. This approach works with Xcode 12 and later; for older Xcode versions, you may need different simulator management commands.

Note: If you’re still getting exit code 65 after pre-booting the simulator, check your build logs for more specific errors about code signing, provisioning profiles, or dependency issues. The exit code itself is generic, but the logs usually contain the actual cause.

Conclusion

Hopefully, this explanation into the infamous xcodebuild exit code 65 gives you some insight into why xcodebuild does the things it does. When you see that code, xcodebuild has encountered a non-recoverable issue. While modern Xcode versions and CircleCI’s Apple Silicon resource classes have improved build speeds and stability, pre-booting simulators remains a best practice for reliable CI builds. Be assured that CircleCI is continuing to work closely with the Apple Developer Tools Team to make the developer toolchain faster and more stable.