Building Apple clients on mac-builder

VerifiedCaution

Build, test, and deploy Apple clients (iOS/tvOS/macOS) from Linux using a remote Mac via SSH and XcodeBuildMCP. Covers syncing, XcodeGen, simulator/device builds, signing, and error reading.

Sby Skills Guide Bot
DevelopmentAdvanced
8/7/2026
Claude CodeCursor
#ios#remote-build#xcodebuild#ssh#code-signing

Recommended for

Automated summary

This skill drives building, testing, and deploying the Silo Apple clients (iOS, tvOS, macOS) from a Linux workstation with no Xcode, by running all Apple toolchain work on a remote Mac over SSH and XcodeBuildMCP.

Strengths

  • Specifies an rsync/patch sync procedure with mandatory verification of commit and git status before any build
  • Clarifies the split between the XcodeBuildMCP channel and ad-hoc ssh commands, and why state does not carry between them
  • Covers real pitfalls: XcodeGen regeneration, the keychain signing trap, reading genuine build errors, and on-device log capture
  • Provides copy-pasteable commands with absolute paths suited to a non-interactive SSH session

Limitations

  • Tightly coupled to the Silo repo: paths, SSH alias, and schemes are hardcoded and not reusable as-is
  • Assumes the remote Mac and MCP server are already configured, deferring setup details to external documentation
  • Deliberately excludes Swift source editing and server or Android work
When to use it

Use it whenever you need to build, test, sign, or deploy a Silo Apple app from the Linux host, or diagnose a build failure on the remote Mac.

When not to use it

Do not use it to edit Swift source locally, or for any server-side or Android work.

Security analysis

Caution
Quality score85/100

This is a straightforward internal build-workflow guide for a Linux-to-Mac SSH pipeline. The commands it prescribes (rsync, ssh, xcodegen, xcodebuild, simctl/devicectl via MCP) are powerful but used for their normal, documented purpose, and the document actively guards against leaking hardware identifiers. The only real risk is operator error around rsync --delete and keychain/signing operations on a declared scratch checkout, which warrants 'caution' rather than 'safe' or 'risky'.

Findings
  • •Instructs running remote commands over SSH (ssh mac-builder '<command>') and rsync with --delete against ~/silo-apple-deploy; the delete is scoped to a build-scratch checkout, but a mistyped path could remove local/remote work.
  • •Involves code-signing and keychain-unlocking operations on the Mac, which touch credentials/signing identities (legitimate, but sensitive).
  • •Guides the agent to perform git checkout -- . on the remote tree, which discards uncommitted remote changes (intended, since the remote is declared scratch).
  • •No evidence of exfiltration: the doc explicitly warns against committing device UUIDs/personal hardware identifiers and does not send secrets to third parties.
  • •No obfuscated payloads, curl|sh, or safety-disabling instructions.
How this analysis is made

Examples

Build for simulator
Build the Silo iOS app for the simulator on mac-builder and tell me if it compiles.
Sync and regenerate
Sync my current working tree to mac-builder, verify the commit matches local HEAD, and regenerate the Xcode project with XcodeGen.
Debug a signing failure
The device build fails with a code signing error after my last sync — check the keychain setup on mac-builder and show me the real xcodebuild error.

name: mac-builder description: Use when building, testing, running, or deploying the Silo Apple clients (iOS, tvOS, macOS) from the Linux workstation, which has no Xcode — all Apple toolchain work runs on the remote mac-builder Mac over SSH and XcodeBuildMCP. Covers syncing the working tree to the Mac, XcodeGen, simulator and device builds, code signing and the keychain trap, reading real build errors, and on-device log capture. Not for editing Swift source (do that locally) and not for server or Android work.

Building the Apple clients on mac-builder

This repo is developed on a Linux host with no Xcode. Every xcodebuild, simctl, devicectl, and signing operation runs on a remote Mac reached through the SSH alias mac-builder.

Read this before the first build in a session. Full background, one-time setup, and the rationale behind each rule: docs/mac-builder.md.

Two channels, and why the difference matters

  1. XcodeBuildMCP (mcp__xcodebuildmcp__*) — a long-lived stdio MCP server running on the Mac over SSH. Structured build/test/device/simulator/UI-automation/LLDB tools. Prefer these.
  2. Ad-hoc ssh mac-builder '<command>' — for what MCP doesn't cover: xcodegen, keychain unlocking, reading raw build logs, devicectl --console.

These are separate SSH sessions. State set in one does not apply to the other. That single fact causes the worst failure in this workflow — see Signing.

If MCP tools are missing entirely, the server isn't connected; fall back to ad-hoc SSH and tell the user. If only some tools exist (simulator but no device/macOS/debug), the wrapper's XCODEBUILDMCP_ENABLED_WORKFLOWS is wrong — see docs/mac-builder.md.

Step 1 — Sync the working tree, and verify it

The Mac has its own checkout at ~/silo-apple-deploy. It is build scratch. Never edit source there, never commit from it. Local Linux checkout is the source of truth.

A build result from a stale tree is worse than no build. Always sync, always verify.

Full sync — first sync of a session, after a branch switch, or after large changes:

rsync -az --delete --exclude 'DerivedData' --exclude 'build' \
  /srv/dev/github/SiloServer/silo-apple/ mac-builder:~/silo-apple-deploy/

Incremental sync — the fast path while iterating (seconds, not tens of seconds). Diff against a fixed base commit and reset first, so each push is the full cumulative delta:

git diff <base-commit> > /tmp/silo.patch
ssh mac-builder 'cd ~/silo-apple-deploy && git checkout -- .'
ssh mac-builder 'cd ~/silo-apple-deploy && git apply - && git status --short' < /tmp/silo.patch

Then verify — do not skip this:

ssh mac-builder 'cd ~/silo-apple-deploy && git log --oneline -1 && git status --short | wc -l'

The commit must match local HEAD and the count must match local git status --short | wc -l. If either differs, fix the sync before building. Do not report a build result from an unverified tree.

Step 2 — Regenerate the project

Silo.xcodeproj is generated by XcodeGen and is not committed. A stale one silently omits new files, producing confusing "cannot find type" errors.

ssh mac-builder 'cd ~/silo-apple-deploy/iosApp && /opt/homebrew/bin/xcodegen generate'

Required after any sync touching project.yml, adding/removing files, or changing signing xcconfigs. Cheap — just run it after every sync. Use the absolute path: a non-interactive SSH session has no Homebrew on PATH.

Step 3 — Session defaults

session_show_defaults          # always call before the first build in a session

If unset, set them once so later calls take no arguments:

// session_set_defaults
{
  "projectPath": "/Users/<mac-user>/silo-apple-deploy/iosApp/Silo.xcodeproj",
  "scheme": "Silo",
  "simulatorId": "<uuid from list_sims>",
  "configuration": "Debug"
}

Get <mac-user> from ssh mac-builder pwd. Discover IDs with list_sims / list_devices — never hardcode simulator or device UUIDs; they change on runtime reinstall and re-pairing, and device IDs are personal hardware identifiers that must not be committed to this public repo.

Schemes: Silo (iOS), SiloTV (tvOS), SiloMac (macOS).

Building and testing

Simulator — the default

Needs no developer signing identity, so it never hits the login-keychain trap. Normal simulator build/run tools still apply Xcode's local ad-hoc signature and simulated entitlements. Keep that signature for any flow that signs in: Silo stores its session in the Keychain, and an installed CODE_SIGNING_ALLOWED=NO product has no Keychain entitlement, so authentication exists only in memory and disappears on the next launch.

| Intent | Tool | |---|---| | Build | build_sim | | Build and run in one call | build_run_sim (prefer over build + launch — fewer SSH round trips) | | Unit tests | test_sim | | tvOS / macOS | same tools with {"scheme": "SiloTV"} / build_macos, test_macos | | Screenshot, UI tree | screenshot, snapshot_ui | | Interact | tap, type_text, swipe, button | | Debug | debug_attach_sim, debug_breakpoint_add, debug_stack, debug_variables |

Fast compile check, no signing

Quickest confirmation that a platform still builds:

ssh mac-builder 'cd ~/silo-apple-deploy/iosApp && \
  xcodebuild build -project Silo.xcodeproj -scheme SiloTV \
  -destination "generic/platform=tvOS" CODE_SIGNING_ALLOWED=NO 2>&1 \
  | grep -E "error:|BUILD (SUCCEEDED|FAILED)"'

-scheme SiloMac -destination "platform=macOS" for the Mac app.

This is compile-only. Never install or launch this unsigned product for authenticated UI validation. Use build_run_sim or a normal simulator xcodebuild without CODE_SIGNING_ALLOWED=NO, reuse the same simulator, and verify the built app contains simulated application-identifier / keychain-access-groups entitlements before asking the user to sign in. Once authenticated, prefer stop/launch over reinstalling when the binary has not changed.

Physical device

Requires real signing. Discover the device first:

ssh mac-builder 'xcrun devicectl list devices'

An Apple TV reads unavailable while asleep — ask the user to wake it rather than retrying.

Build with the keychain unlock in the same SSH command (see below), then:

ssh mac-builder 'xcrun devicectl device install app --device <DEVICE_ID> \
  ~/silo-build-ios/Build/Products/Debug-iphoneos/Silo.app'
ssh mac-builder 'xcrun devicectl device process launch --device <DEVICE_ID> org.siloserver.silo'

Use the stable derived-data paths ~/silo-build-ios / ~/silo-build-tvos so incremental builds stay warm and product paths are predictable. tvOS products land in Debug-appletvos — installing a Debug-appletvsimulator bundle fails confusingly, so never mix destinations.

Signing and the keychain trap

security unlock-keychain does not persist across SSH sessions. Each connection is its own security session. Unlocking in one ssh call has no effect on the next one, and none on the already-running MCP server — a different, long-lived session.

Symptom: the build dies in a CodeSign step, often on an embedded extension rather than the app. MCP output shows only "Command CodeSign failed"; the real cause (User interaction is not allowed) is in the raw log.

Rules:

  • Normally signed simulator builds are unaffected. Xcode uses "Sign to Run Locally" and does not need the developer login keychain.
  • CODE_SIGNING_ALLOWED=NO is compile-only. It is safe for build checks, but an installed Silo product cannot persist its Keychain-backed login across relaunches.
  • Device builds: unlock inside the same ssh command as xcodebuild:
ssh mac-builder 'security unlock-keychain -p "$KEYCHAIN_PASSWORD" ~/Library/Keychains/login.keychain-db && \
  cd ~/silo-apple-deploy/iosApp && \
  xcodebuild build -project Silo.xcodeproj -scheme Silo \
    -destination "id=<DEVICE_ID>" -derivedDataPath ~/silo-build-ios \
    DEVELOPMENT_TEAM=<TEAM_ID> -allowProvisioningUpdates 2>&1 | tail -30'
  • If codesign still fails right after a successful unlock, the partition list is blocking non-interactive access:
ssh mac-builder 'security unlock-keychain -p "$KEYCHAIN_PASSWORD" ~/Library/Keychains/login.keychain-db && \
  security set-key-partition-list -S apple-tool:,apple:,codesign: -s \
    -k "$KEYCHAIN_PASSWORD" ~/Library/Keychains/login.keychain-db'
  • Cheap isolation test instead of a five-minute build:
ssh mac-builder 'security find-identity -v -p codesigning'
ssh mac-builder 'cp /bin/echo /tmp/cs-probe; codesign --force --sign <IDENTITY_HASH> /tmp/cs-probe; rm -f /tmp/cs-probe'

Watch for CSSMERR_TP_CERT_REVOKED — revoked certs linger in the keychain and get picked ahead of the good one.

Secret handling. The keychain password and team ID are not stored on the Linux host and must not be written into this repo. Ask the user, or have them unlock the keychain out-of-band. Never inline a password in a command — agent sessions write full transcripts to disk. If a device build isn't essential to the task, use a simulator or unsigned build and say so.

Signing configuration is in-repo: iosApp/Signing/Local.xcconfig.sample → Local.xcconfig (gitignored) overrides bundle IDs, entitlements, and DEVELOPMENT_TEAM for Personal Teams. Run xcodegen generate after editing it.

Reading the real build errors

MCP output is summarized and truncated. Full logs are on the Mac; the tool result gives the exact path under ~/Library/Developer/XcodeBuildMCP/workspaces/<workspace>-<hash>/logs/.

ssh mac-builder 'grep -E "error:|CSSMERR|Command CodeSign" "<log-path>" | head -20'
ssh mac-builder 'grep -B5 -A25 "CodeSign /Users.*<Target>" "<log-path>"'   # signing failures

For raw xcodebuild output, always filter with grep -E "error:" — real errors hide under thousands of lines of toolchain noise. Do not diagnose from the tail alone.

Capturing device logs

OSLog does not reach devicectl --console; print() to stdout does.

ssh mac-builder 'nohup xcrun devicectl device process launch \
  --device <DEVICE_ID> --console org.siloserver.silo \
  >> /tmp/silo-run.log 2>&1 </dev/null & disown'

All three of nohup, </dev/null, and disown are required, or a SIGHUP / SIGTTIN stop / shell exit kills the stream.

Tail with tail -n 0 -F (capital -F survives truncation) piped through grep --line-buffered — without line buffering, events arrive in minute-long bursts.

Closing the --console stream terminates the app on the device. Never pkill that process mid-test; if you do, you caused the App terminated due to signal 15 and must relaunch. --console is launch-time only — you cannot attach to an app the user is already using.

Checklist before reporting a result

  • [ ] Synced, and commit + dirty-file count verified against local
  • [ ] xcodegen generate run after the sync
  • [ ] Errors read from the full log, not the truncated tool output
  • [ ] Destination matches the claim (simulator build ≠ device verification)
  • [ ] Authenticated simulator runs use a normally signed product, not CODE_SIGNING_ALLOWED=NO
  • [ ] Nothing committed or edited on the Mac

When the simulator misbehaves, check the disk first

A near-full boot volume on the Mac does not announce itself. It presents as CoreSimulator corruption, and every symptom points somewhere else:

  • xcrun simctl install hangs forever on a ~100 MB app.
  • A booted device shuts itself down between two commands.
  • simctl boot reports "delete the device properly or erase contents and settings".
  • ps / pgrep block, because simulator processes are wedged in kernel wait.
  • A stray simctl diagnose sits holding a device lock — a downstream symptom, not the cause.
ssh mac-builder 'df -h /System/Volumes/Data'

Below ~15 GiB free, expect trouble. ~/Library/Developer/CoreSimulator/Devices runs to tens of GB on its own. Reclaim with xcrun simctl erase <udid> for devices you aren't using (this wipes their app data and any signed-in session) and xcrun simctl delete unavailable.

~/silo-build-ios and ~/silo-build-tvos are symlinks to /Volumes/NVMe/..., which has far more room than the boot volume. The documented paths above still work unchanged — don't convert them back into real directories on /.

Gotchas

  • SourceKit "No such module" errors are IDE index artifacts. If xcodebuild says BUILD SUCCEEDED, the code is fine.
  • mac-builder's Tailscale direct path sometimes drops and falls back to a lossy DERP relay. SSH then times out while tailscale ping mac-builder still answers (via DERP, ~300ms). The machine is not down; it recovers on its own. Connecting by raw Tailscale IP may work while the hostname does not.
  • Device install can fail on a Developer Disk Image mismatch when the device OS is newer than the Mac's Xcode. Updating Xcode is the fix; the build isn't wrong.
  • Homebrew is not on the non-interactive SSH PATH. Use absolute paths: /opt/homebrew/bin/xcodegen, /opt/homebrew/bin/xcodebuildmcp.
  • Commits, pushes, and PRs happen on Linux against the real working tree — never from ~/silo-apple-deploy.
  • Codex memory may reference a helper at /home/dev/.local/bin/silo-apple-deploy. That script no longer exists. Use the sync steps above.

Not for this skill

  • Editing Swift source — do that in the local checkout, then sync.
  • Silo server or Android work.
  • Client bugs reproducible without a build.

From the file to a first result

The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.

View the source and supporting files

This download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.

1. Download the file

Download SKILL.md

From the root of a test project, save the file as .skill-downloads/building-apple-clients-on-mac-builder-msih94vt/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.

Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.

mkdir -p '.skill-downloads/building-apple-clients-on-mac-builder-msih94vt' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/building-apple-clients-on-mac-builder-msih94vt/download' --output '.skill-downloads/building-apple-clients-on-mac-builder-msih94vt/SKILL.md.part' && mv '.skill-downloads/building-apple-clients-on-mac-builder-msih94vt/SKILL.md.part' '.skill-downloads/building-apple-clients-on-mac-builder-msih94vt/SKILL.md'

2. Check prerequisites

Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.

No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.

The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.

3. Load the skill

After inspection, copy the complete folder into your project using one of the commands below, then open a new tool session. The command stops if a folder with the same name already exists: compare the files before replacing it.

Claude Code
test -f '.skill-downloads/building-apple-clients-on-mac-builder-msih94vt/SKILL.md' && test -s '.skill-downloads/building-apple-clients-on-mac-builder-msih94vt/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/mac-builder' && cp -R '.skill-downloads/building-apple-clients-on-mac-builder-msih94vt' '.claude/skills/mac-builder'

Cursor
test -f '.skill-downloads/building-apple-clients-on-mac-builder-msih94vt/SKILL.md' && test -s '.skill-downloads/building-apple-clients-on-mac-builder-msih94vt/SKILL.md' && mkdir -p '.cursor/skills' && test ! -e '.cursor/skills/mac-builder' && cp -R '.skill-downloads/building-apple-clients-on-mac-builder-msih94vt' '.cursor/skills/mac-builder'

OpenSkills

OpenSkills must already be installed. The local install/read procedure is checked with OpenSkills 1.5.0, which requires Node.js ≥ 20.6.0. Catalog slugs are not resolved automatically.

Official OpenSkills installation
openskills install './.skill-downloads/building-apple-clients-on-mac-builder-msih94vt'

openskills read 'building-apple-clients-on-mac-builder-msih94vt'

Reading should display the installed file content and its base directory. It does not run the skill or install its dependencies.

For an agent that reads AGENTS.md, then sync the index. This command changes AGENTS.md: review the diff before using it.

openskills sync

Manual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.

4. Try a first task

Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:

Read .skill-downloads/building-apple-clients-on-mac-builder-msih94vt/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.

Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.

Related skills