NoMacSign in
← Blog

Debug a failing iOS CI build on a cloud Mac

Stop pushing commits to guess at a CI failure. Reproduce it on a clean macOS VM, let your agent bisect the fix, then send one commit back to CI.

· 8 min read

Need a Mac for this? NoMac gives your agent full macOS with Xcode, from $9.99 a month, billed by the second. Start now →


Every iOS developer knows this loop. The build passes on your machine and fails in CI. You read the log, guess, push a commit called “fix ci”, wait twelve minutes, and read a slightly different log. Five commits later you have fixed it, or given up and added a retry.

If you do not own a Mac, it is worse: you cannot even reproduce the passing build locally. The faster path is to reproduce the CI environment on a clean Mac you can talk to, fix it there in minutes, and send one commit back.

Why a fresh VM is the right place: CI runners start clean. A clean macOS VM reproduces the failures your laptop hides: files that are not committed, schemes that are not shared, caches only you have, signing settings tied to one keychain.

1. Start from exactly what CI built

git clone --branch failing-branch https://github.com/you/MyApp.git
cd MyApp
nomac start --json
nomac sync .

Clone fresh rather than syncing your working copy. If the failure is “works on my machine”, your working copy is the thing hiding it.

2. Match the toolchain

nomac ssh -- xcodebuild -version
nomac ssh -- xcrun simctl list runtimes

Compare with the Xcode version your CI job selects. A surprising number of CI failures are simply a different Xcode: a new warning promoted to an error, a changed Swift concurrency check, or a deployment target the older SDK does not know. If versions differ, note it: that alone may be the answer.

3. Run the exact CI command

Copy the build command from the CI log, not from memory. Flags matter: -destination, -configuration, -derivedDataPath, CODE_SIGNING_ALLOWED=NO.

nomac ssh -- xcodebuild -workspace MyApp.xcworkspace -scheme MyApp \
  -configuration Release -destination 'generic/platform=iOS' \
  CODE_SIGNING_ALLOWED=NO build 2>&1 | tail -80

If it fails the same way, you have a reproduction and can iterate in seconds rather than pushes.

4. The usual suspects

Swift Package resolution

Errors like Could not resolve package dependencies usually mean Package.resolved is missing or not committed, a private package needs credentials CI does not have, or a branch dependency moved. Run xcodebuild -resolvePackageDependencies on its own to isolate it.

CocoaPods

A missing Pods directory, a Podfile.lock mismatch, or a different CocoaPods or Ruby version. Run bundle exec pod install --deployment to see the same error CI sees.

A scheme that is not shared

xcodebuild: error: The project does not contain a scheme named ... means the scheme lives in your user data, not in xcshareddata. Mark it shared and commit it.

Files that only exist on your machine

A missing GoogleService-Info.plist, a generated file, or a config that is in .gitignore. The fresh clone reproduces this immediately.

Signing in a job that should not sign

Errors about provisioning profiles in a test or analysis job. Build with CODE_SIGNING_ALLOWED=NO for anything that does not ship. See code signing errors, decoded for the rest.

Out of memory or time

A build that dies without an error, or is killed at a round number of minutes, is usually the runner, not your code. Check the CI job's timeout and machine size before touching source.

5. Let your agent bisect it

This is a good job for an agent: it is repetitive, the success condition is clear, and it can try things you would not bother to. Give it the reproduction and the rule:

The CI build fails; the reproduction is on the NoMac session.
Change one thing at a time, sync, rerun the exact CI command, and record the result.
Stop when it passes. Then summarise the minimal fix as one commit.
Do not change CI configuration unless the source cannot be fixed.

6. Send one commit back

Once it passes on the clean Mac, commit the fix, push once, and let CI confirm. Then stop the session:

nomac stop --json
  • ·A dozen failed CI runs cost more minutes than one short interactive session.
  • ·Your Git history keeps one meaningful commit instead of six guesses.
  • ·You learn why it failed, which is what stops it happening again.

CI stays the source of truth; the session is a workbench. For the difference in shape, see NoMac vs GitHub Actions macOS runners.

Give your agent a Mac in a minute.

Full macOS with Xcode and root, over MCP, CLI, API or SSH. Billed by the second, deleted when you stop. No Apple account needed to start.

Get started for $9.99

7-day money-back guarantee on your first purchase. Cancel anytime.

Connect your agent →