After a team checks out a project on a cloud Mac, the most commonly underestimated failure is not a compilation error. It is the pipeline failing to find the expected scheme—or running different targets from a scheme with the same name in the remote environment. Local Xcode reads user-specific settings from the developer directory, while a clean CI workspace only sees files committed to the repository. To keep the build entry point stable, schemes must be version-controlled and validated just like scripts and dependency lockfiles.
Treat the Scheme as a CI Interface
A scheme invoked by a pipeline defines at least four things: which targets to build, which build configuration to use, which test bundles to run, and which configuration to apply when archiving. Seeing its name in the Xcode menu does not mean this information has been committed.
First, check whether the shared file exists:
find . \( -path "*/xcshareddata/xcschemes/*.xcscheme" \
-o -path "*/xcuserdata/*/xcschemes/*.xcscheme" \) -print
A scheme under xcuserdata is user-specific and cannot serve as a CI entry point. Enable Shared in Xcode’s Manage Schemes window, then confirm that the file appears at one of these paths:
App.xcodeproj/xcshareddata/xcschemes/App.xcscheme
App.xcworkspace/xcshareddata/xcschemes/App.xcscheme
Next, use git status and git check-ignore -v to confirm that the file is tracked and has not been accidentally excluded by an overly broad *.xcuser* or xcshareddata ignore rule.
“Visible locally” only means the configuration exists in the current user’s directory. “Available to CI” must be demonstrated by command-line results from a clean checkout.
Pin the Workspace, Scheme, and Configuration
The pipeline should not rely on Xcode to select a project automatically. When CocoaPods, aggregate projects, or local packages are involved, opening the .xcodeproj can produce different results from opening the .xcworkspace. Define all three entry-point values explicitly as CI variables:
WORKSPACE="App.xcworkspace"
SCHEME="App"
CONFIGURATION="Release"
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration "$CONFIGURATION" \
-list
The output from -list should include the expected scheme. Then use JSON output for machine validation instead of relying solely on someone reading the logs:
xcodebuild -workspace "$WORKSPACE" -list -json > xcode-list.json
python3 - <<'PY'
import json
data = json.load(open("xcode-list.json"))
schemes = data.get("workspace", {}).get("schemes", [])
if "App" not in schemes:
raise SystemExit("Required Xcode scheme is missing")
PY
If the repository keeps separate entry points for development, staging, and production, their names should describe their purpose—for example, App-Dev, App-Staging, and App-Release. Do not let a script select the first scheme based on list order; adding a dependency can change that order.
Validate Build, Test, and Archive Separately
Sharing a scheme only makes it visible; it does not prove that its actions are correct. Split validation into three layers.
Inspect the Resolved Build Settings
First, export the final settings. Pay particular attention to PRODUCT_BUNDLE_IDENTIFIER, CONFIGURATION, SDKROOT, SUPPORTED_PLATFORMS, and the output directories:
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration "$CONFIGURATION" \
-showBuildSettings > build-settings.txt
grep -E "PRODUCT_BUNDLE_IDENTIFIER|CONFIGURATION =|SDKROOT|SUPPORTED_PLATFORMS" \
build-settings.txt
These values reflect the merged result of the scheme, project settings, and command-line arguments, so they are closer to the actual build than a direct inspection of project.pbxproj.
Validate the Test Entry Point
During the test stage, build the testable products before running the tests. This separates a broken compilation entry point from a test execution failure:
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-destination "platform=iOS Simulator,name=iPhone 16" \
build-for-testing
The simulator name must come from xcrun simctl list devices available on the current machine. Do not hard-code a device that exists only on a developer’s computer. If the team needs to pin a runtime, record both the Xcode version and the target OS version.
Validate the Archive Action Separately
Archive may use a different configuration from Run or Test. First verify the ArchiveAction configuration in the .xcscheme, then perform a real archive in a controlled job:
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration Release \
-destination "generic/platform=iOS" \
-archivePath "$PWD/artifacts/App.xcarchive" \
archive
Do not treat a successful regular build as a substitute for archive validation. Resource processing, script phases, and archive settings often reveal problems only at this stage.
Clear Local State and Validate Again
The most reliable check is not deleting DerivedData, but cloning the repository again into a new directory. This exposes uncommitted schemes, local symbolic links, untracked configuration, and implicit dependencies at the same time:
ROOT="$(mktemp -d)"
git clone --no-local . "$ROOT/repo"
cd "$ROOT/repo"
git submodule update --init --recursive
xcodebuild -workspace App.xcworkspace -scheme App -list
If the project depends on a generation step, run its command before the build and verify that the generated output is reproducible. Do not put “open the project once in Xcode” in the runbook. Such a step cannot be part of an unattended job and can conceal missing files.
Clean-room validation should cover at least the following:
| Check | Passing criterion |
|---|---|
| Scheme visibility | xcodebuild -list -json returns the specified name |
| File ownership | The scheme is under xcshareddata and tracked by Git |
| Build configuration | Command-line-resolved settings match the pipeline’s expectations |
| Test entry point | build-for-testing produces testable products |
| Archive entry point | A generic iOS destination produces an .xcarchive |
| Non-interactive execution | No need to open Xcode or read from a user directory |
Block Entry-Point Drift Before Merge
Schemes are XML files, and merge conflicts are often handled like ordinary text conflicts. The result may preserve the scheme name while losing a Testable or BuildActionEntry. Add lightweight checks to merge requests: verify that the shared file exists, reject committed xcuserdata, and run xcodebuild -list -json.
Also review the scheme’s Pre-actions and Post-actions. Remote execution will still fail if a script refers to absolute paths, interactive shell configuration, or tools installed only on one developer’s machine. Scripts should locate files through build variables such as SRCROOT and exit with a clear error when a dependency is missing.
The final acceptance criterion is straightforward: starting from an empty directory, obtain the repository and provide only the documented Xcode version and commands. The job must then be able to enumerate schemes, resolve settings, build testable products, and complete an archive. Only at that point is the cloud Mac on G-Mini executing the entry point defined by the repository rather than reproducing the accidental state of a developer’s computer.
Frequently asked questions
Why can Xcode run locally while cloud Mac CI cannot find the scheme?
The scheme is usually stored under xcuserdata instead of xcshareddata/xcschemes. A clean CI checkout has no access to developer-specific Xcode data, so the shared scheme file must be committed.
Does sharing an Xcode scheme prove that Archive will work?
No. Sharing only makes the scheme visible. Validate the Release configuration, archive target, pre-actions, and environment assumptions, then complete at least one archive from a clean checkout.
Cloud Mac for your next build queue
Compare two M4 configurations across five nodes, then choose daily, weekly, monthly, or quarterly rental based on your workflow.