Repository navigation
feat: dev loop bundles 6-12 (reporting, safe calls, test results, run loop, UI driving, inner loop, tool surface) - #6
Merged
Conversation
Route every xcodebuild call through one entry point that applies per-call options (DerivedData path, exact result bundle, extra args), holds an optional first-come, first-served build lock compatible with lockf(1), and warns on low disk or heavy swap. - simRecovery defaults to off; auto only reboots, erase must be asked for - never pkill every xcodebuild; hang snapshots match this run's bundle - WDA port cleanup only kills WDA runners on the configured local port - collision-free result bundle names; per-project last-result records used by build diagnose, test failures and test coverage - --isolated-sim runs on a throwaway simulator - xcforge lock status / build_lock_status Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NVNakyntY4u9MvhsjbzRhZ
…sting tests Adds an idle timeout (600s of no output) to every build and test xcodebuild, raises the total limit to 1800s, retries test-without-building once when the runner never starts, continues building after errors and reads diagnostics from stdout, lists tests through JSON enumeration (Swift Testing included, with --testplan), and fixes test plan discovery and decoding. Also fixes the Bundle 1 test that failed to type-check and a lint nit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NVNakyntY4u9MvhsjbzRhZ
…ice Hub Adds wda start/status/stop (CLI and MCP) to build, sign, launch and reach xcforgeWDA on a device, device screenshots, deep links and env vars on device launch, clear messages for locked devices, UI Automation off and signing failures, and stops simulator self-heal from running against device targets. Opens Device Hub when Simulator.app is gone and accepts both apps in capture and accessibility code. Raises xcforgeWDA's deployment target to iOS 15. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NVNakyntY4u9MvhsjbzRhZ
Auto-promotion becomes opt-in, xcforge's own build flags can be turned off, lldb continue and simulator launch timeouts are longer and configurable, bless stops suggesting commit messages, and tool text and the skill lose preference claims and marketing copy. Fixes stale paths in CLAUDE.md. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NVNakyntY4u9MvhsjbzRhZ
Suites that stub WDA's localhost URL ran in parallel and could answer each other's requests. A shared async gate now holds each stubbed region. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NVNakyntY4u9MvhsjbzRhZ
- Cancelling an MCP call, a timeout, or xcforge exiting now kills the whole child process tree, so xcodebuild no longer keeps DerivedData locked. - MCP calls with a progress token get a notification every 10s with the latest output line, so clients don't time out long builds. - xcodebuild calls on the same DerivedData or project queue in-process. - An explicit project uses its own repo's .xcforge.yaml; results name the project used; yaml quotes and comments are handled and problems warned. - Unknown MCP arguments are rejected with the closest real name. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
xcforge-web | dee6828 | Oct 09 2026, 09:38 PM |
justinthevoid
marked this pull request as ready for review
October 9, 2026 15:44
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
- One test ID format (Target/Suite/test()) in results, list_tests, last-failures and known-failures; filters accept it as printed, and short IDs get the target from the scheme or test plan - Every failure message with file and line, argument/repetition label, attachments and optional console tail; identical messages grouped, text output capped with a count of the rest - Run options on test_sim/build_and_test and the CLI: retries, iterations, until-failure, parallel, per-test timeout, skip build - Flaky tests (failed then passed on retry) reported - rerun-failed reuses recorded run settings, skips the build when no source changed, and refuses when the last run failed before any test - Test failures are no longer reported as a test target build failure; agent JSON gives a reason when no test reported Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
- CLI test run shows a failed test build as compile errors with file and line; test_sim uses the parsed build errors too - test failures never runs tests: it reads the last test run, says when there is none, and returns compile errors when that run's build-for-testing failed instead of an older run's failures - build-for-testing passes the test plan and builds only the targets a filter names; the last-build record tracks plan and targets - build compile, build-for-testing, test and test list share build flags - hang watchdog samples at 300s/540s and only reports on timeout or diagnose - build compile skips -showBuildSettings and compiles without a booted simulator - one simulator name lookup everywhere (booted first, newest OS, no prefix matches) - a zero-match filter lists suggestions without rebuilding - agent JSON carries the result bundle and the timeout reason - build run exits non-zero when the app didn't launch Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
- launch_app, build_run_sim, sim launch and build run check the app is still running 2s after launch and report the crash (exception, reason, top frames, .ips path) when it isn't - Launch args, env, deep link and terminate options; new open_url and sim openurl - build_run_sim boots after a successful build, waits for boot, and installs the scheme's application target from -showBuildSettings -json - CLI log and console capture run in the background and survive the start command; reads default to the newest 200 lines and say what was left out; wait_for_log no longer clears the buffer - crashes topic matches error/fault lines instead of default-level ones - Auto-detect ignores the workspace inside .xcodeproj and picks the project-named scheme; clean takes a configuration and can delete this project's DerivedData - A new process loads the last build's bundle ID and app path Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
- Every UI tool takes simulator; each simulator gets its own WDA port (saved in ~/.xcforge/wda/simulator-ports.json), XCFORGE_SIMULATOR for the CLI - Recreated WDA sessions send forceAppLaunch:false; restart reruns the cached xctestrun with test-without-building and the runner has no one-hour cap - The AXP fast path runs only with one simulator booted and no phone, reads that simulator's windows, and get_source/list_elements start WDA instead of falling back to Simulator.app - Smaller trees: empty wrappers, hidden and off-screen nodes dropped; value, disabled and selected shown - find_element reports every match and takes index/timeout/until_gone; actions take wait_for, until_gone and timeout - type_text types into the focused field, with key, dismiss_keyboard and secure (redacted) options - alert_action / XCFORGE_ALERT_ACTION and a visible-alert hint in not-found errors - Screenshots report device points, take crop and max_dimension, and never share a fixed /tmp file - sim_content_size, sim_locale, app_container, sim_push, sim_privacy (and sim content-size|locale|container|push|privacy) - Accessibility, localization and multi-device checks restore the text size, language and appearance they found - Coordinate taps and swipes account for interface orientation Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
- build typecheck / build_typecheck compiles one target (its scheme, else -target) - --from-snapshot builds a git-worktree snapshot of the working tree; errors map back - lsp setup / lsp_setup writes buildServer.json pointed at xcforge's DerivedData - --jobs, --all-errors with a separate diagnostic DerivedData slot - SwiftPM tools find the package (path, yaml packagePath, cwd, only package in repo), parse errors and test failures, and take the build lock and idle timeout Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
- tap replaces eight tap tools; swipe takes hid, get_source takes format: list, find_element takes all, screenshot takes device. Old names stay callable for one release with a note naming the replacement - The diagnose group starts off; XCFORGE_TOOL_GROUPS / yaml toolGroups pick the starting groups, and tool_groups changes send notifications/tools/list_changed - Every listed argument is camelCase; snake_case is still accepted - build_compile, build_sim, build_typecheck, test_sim and build_and_test return compact JSON by default over MCP (for: human for text); diagnose and plan JSON are compact - Read-only tools carry readOnlyHint - test_sim rerunFailed - plan decide resumes from a saved session in a new CLI process - defaults set finds the project first and fails when nothing is saved; a saved simulator that no longer exists is ignored - Last results keyed on the canonical project path, written under a lock - xcforge --version; CLI JSON errors on stdout - Docs: tool-surface reference, counts checked by a test Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
Device Hub's devicectl lists simulators alongside phones. Drop entries that report a non-physical reality, a simulator platform or device type, or a UDID simctl knows, and read the state from the tunnel instead of visibilityClass. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
properties groups fields as hardware, state and connection; simulators report reality: simulated, visibilityClass: simulators and a CoreSimulator provider. Tests use shapes captured from Xcode 27.2. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
- typecheck --target builds into the configured DerivedData via SYMROOT/OBJROOT; xcodebuild usage errors are reported as xcodebuild_usage - HID input finds SimulatorKit in Xcode 27's SharedFrameworks; WDA fallbacks say why - launches are watched 8s and wait up to 15s for a late crash report - relative --project paths resolve against the working directory - first WDA start gets time to build and start, and failures name the cause - lock status skips queued xcforge processes; timed-out test runs aren't miscounted; unreported snapshots and old artifacts are cleaned up - landscape screenshot sizes, --simulator on every ui command, JSON errors with --json, bless help text Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
The stub answered every loopback request, so another suite's ungated createSession against 127.0.0.1:1 could take the queued POST /session response and fail the healthy-path test. It now intercepts port 8100 only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
--help, help <command> and --version end in a parser error with a success exit code; the JSON error path wrapped it as execution_failed and exited 1 whenever output was piped. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
On a loaded runner the 10ms deadline's capture hadn't finished after 200ms, so the result and file were missing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
lookup:error: returns a mach_port_t; calling it through perform() treated the port as an object and crashed ui tap. Call it by its real signature, and return an error for an unexpected one. Screenshot point sizes now follow the interface orientation from WDA, since the framebuffer stays portrait. Document that the lock queue orders xcforge waiters only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
wda start spots WDA's "Unable to start web server" log line and fails at once with a --port hint, reports errors as JSON under --json, and builds into the configured DerivedData folder. Starting or restarting the simulator runner stops any xcforgeWDA runner already on that simulator. ui source accepts --format list and rejects unknown formats up front. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
On Xcode 27 IndigoHID events are sent without error but never reach the app, so results said "via IndigoHID" for taps that didn't land. HID is now off on Xcode 27 (XCFORGE_FORCE_HID=1 overrides) and taps and swipes use WDA, saying so in the result. Add iPhone 16 Pro, 17 family and Air screen sizes to the HID table. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
Starting WDA ran xcodebuild against the target simulator, which boots it when it is shut down; with another simulator already booted that left two running, which exhausts memory on a 16 GB Mac. UI commands now fail with "Simulator <name> is shutdown; boot it first" and name any booted one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Bundle 7: calls that don't strand agents
Before: cancelling an MCP call or hitting a timeout killed only the direct child, so
xcodebuildkept running with DerivedData locked ("database is locked" on the next build). Long builds sent nothing back, so some clients timed out. Two parallel calls from one agent raced on the same DerivedData. A project passed explicitly from another repo still used the cwd repo's.xcforge.yaml, quoted values and# commentsin the yaml were read literally, and misspelled MCP arguments were silently ignored.After: cancel, timeout, Ctrl-C, SIGTERM/SIGHUP and the client closing stdin all stop the whole process tree. Calls with a progress token get a heartbeat every 10s carrying the latest output line. xcodebuild calls sharing a DerivedData folder or project queue in-process. An explicit project uses its own repo's yaml, results name the project used, yaml problems come back as warnings with a suggested key, and unknown MCP arguments are an error naming the closest real one.
Bundle 8: test results agents can act on
Before: test IDs came in several spellings, so IDs copied from a result didn't always work as a filter or in known-failures. The target prefix for short filters was guessed. Failures showed one joined message, test failures were reported as "test target build failed", long failure lists printed in full, and there was no way to retry, repeat or time-limit tests.
rerun-failedalways rebuilt, forgot the test plan and env, and "reran" nothing when the last run died before any test.After: one ID format (
Target/Suite/test()/Target/Class/testMethod) everywhere; short IDs get the target from the scheme or test plan. Each failure carries every message with file/line, the argument or repetition label, attachments and optional console tail. Identical messages are grouped and text output is capped at 20. New run options:retries,iterations,untilFailure,parallel,testTimeoutSeconds,skipBuild; tests that pass on retry are listed as flaky.rerun-failedreuses recorded settings, skips the build when no source changed, and refuses when the last run failed before any test.Bundle 6: build and test reporting agents can trust
Before: CLI
test runshowed a test-file compile error as a fake failing test with no file or line.test failuresreturned the previous run's failures after a test build failed, and silently ran the whole suite when nothing was recorded. build-for-testing ignored the test plan and filter, and passed different flags frombuild compile, so alternating rebuilt. The hang watchdog sampled healthy builds at 60s and printed a "Diagnostic snapshot" on success.build compileran an extra-showBuildSettingsand failed unless exactly one simulator was booted, and three simulator-name lookups could pick different devices. A zero-match filter started a second build. Agent JSON had no result-bundle path and no timeout reason when some failures were read.build runexited 0 without launching the app.After: test build failures read as compile errors with file:line in every output.
test failuresreads only the last recorded run, says when there is none, and returns the compile errors of a failed test build. build-for-testing uses the test plan and builds only the targets a filter names. All compiling calls share one flag set. The watchdog samples at 300s/540s and reports only on timeout ordiagnose.build compileskips the settings call and compiles for a booted or the newest iPhone simulator. One simulator lookup everywhere (booted first, newest OS, no prefix matches). Zero-match suggestions come from the last build. Agent JSON addsxcresultand a timeoutreason.build runexits non-zero unless the app is running.Bundle 9: run the app and see what happened
Before: a launch reported success even when the app crashed at startup. There was no way to pass launch arguments, environment or a deep link.
build_run_simcould install an extension instead of the app (it took the last target listed) and launched before the simulator finished booting. CLIlog startandconsole launchcaptures died with the command that started them, reads returned everything,wait_for_logcleared the buffer, and the crashes topic matched default-level lines. Auto-detect could pick the workspace inside.xcodeprojor the wrong scheme.After: launches watch the app for 8s and report the crash (exception, reason, top frames,
.ipspath).args,env,urlandterminateonlaunch_app/build_run_simand the CLI; newopen_url.build_run_simboots after the build, waits for boot, and installs the scheme's application target. CLI captures run in the background and read the newest 200 lines by default;wait_for_logtracks a position.cleantakes a configuration and can delete this project's DerivedData.Bundle 10: UI driving that hits the right screen
Before: all simulators shared WDA port 8100, so with two booted the screenshot showed one and taps went to the other. A recreated WDA session relaunched the app silently, and restarting WDA fell through to a two-minute rebuild. Several fast paths read the whole Simulator app's macOS accessibility tree (menus and every simulator). Trees kept every wrapper and had no value/enabled/selected; lookups took the first of several matches; actions couldn't wait for a result;
type_texttyped into the first text field and echoed passwords. Screenshots reported the Simulator window size as points, came back at full resolution, and the fallback wrote one shared/tmpfile. There were no tools for text size, locale, app containers, push or privacy, and the accessibility/localization checks reset other sessions' settings. Coordinate taps ignored landscape.After: every UI tool takes
simulator, and each simulator gets its own WDA port (XCFORGE_SIMULATORfor the CLI). Recreated sessions sendforceAppLaunch: false; restart reruns the cached xctestrun. The accessibility-tree shortcut runs only with one simulator booted and no phone, scoped to its windows;get_source/list_elementsstart WDA instead of falling back. Trees drop empty wrappers and hidden/off-screen nodes and show value, disabled and selected.find_elementlists every match and takesindex; actions takewait_for,until_gone,timeout.type_texttypes into the focused field, withkey,dismiss_keyboardandsecure.alert_action/XCFORGE_ALERT_ACTION, and not-found errors mention a visible alert. Screenshots report device points and takecropandmax_dimension, with unique file paths. Newsim_content_size,sim_locale,app_container,sim_push,sim_privacy(andsim content-size|locale|container|push|privacy); setters report what they replaced, and the checks restore what they found. Coordinate taps read the interface orientation first.Bundle 11: a faster inner loop
Before: checking one target meant building the whole scheme. A build ran against the live working tree, so edits made mid-build mixed into it. SourceKit-LSP in an editor knew nothing about xcforge's DerivedData. There was no
-jobscontrol and xcodebuild stopped at the first failing target. The SwiftPM tools needed a path, returned raw output, and ignored the build lock and idle timeout.After:
build typecheck/build_typecheckcompiles one target (its scheme if it has one, else-target).--from-snapshotbuilds a git-worktree snapshot of the working tree and maps errors back to the real paths.lsp setup/lsp_setupwritesbuildServer.jsonpointed at xcforge's DerivedData.jobsandallErrors(with its own DerivedData slot so it doesn't evict the normal one). SwiftPM tools find the package (path, yamlpackagePath, cwd, or the only package in the repo), parse errors and test failures, and take the build lock and idle timeout.Bundle 12: a smaller, consistent tool surface
Before: 126 tools listed, eight of them different ways to tap, plus separate tools for HID swipes, flat element lists, finding all matches and device screenshots. Argument names mixed
snake_caseandcamelCase. The ten diagnose tools were always listed. Build tools returned long text over MCP.plan decidefrom the CLI couldn't resume a plan paused by an earlierplan run.defaults setcould report success while saving nothing, and a saved simulator that had been deleted broke every call.After: 104 tools listed (114 with diagnose).
tapreplaces the eight tap tools;swipetakeshid,get_sourcetakesformat: list,find_elementtakesall,screenshottakesdevice. The old names stay callable for one release and their results say what replaces them. Every listed argument is camelCase and snake_case is still accepted. The diagnose group starts off (XCFORGE_TOOL_GROUPSor yamltoolGroupspick the starting groups) andtool_groupschanges sendnotifications/tools/list_changed.build_compile,build_sim,build_typecheck,test_simandbuild_and_testreturn slim JSON by default (for: humanfor text). Read-only tools carryreadOnlyHint.test_simtakesrerunFailed. Paused CLI plans are saved for an hour andplan decideresumes them.defaults setresolves the project first and fails when nothing was saved; a missing saved simulator is dropped with a warning. Last results are keyed on the canonical project path and written under a lock.xcforge --version; CLI JSON errors go to stdout.Fixes from the real-Mac runs (Xcode 27.2, ShutterCoach)
Before:
device listshowed simulators as phones.build typecheck --targetfailed with a configured DerivedData (xcodebuild rejects-derivedDataPathwith-target) and was labelled a compile error. Everyhid: truetap fell back to WDA because Xcode 27 moved SimulatorKit. Crashes 2.6s and 5.5s after launch were reported as running, and late.ipsfiles were never read. A relative--projectbrokelsp setupand--from-snapshot. The first WDA start gave up after about 80s and blamed a missing install.lock statuslisted queued xcforge processes as holders, a timed-out test run counted xcodebuild's own entries as failed tests,--jsonerrors went to stderr as text, watchdog snapshots and result bundles piled up in/tmp, landscape screenshots reported portrait sizes, andset_orientationand mostuicommands couldn't name a simulator.After: simulators are dropped from
device listand the state comes fromtunnelState.-targetbuilds write into the configured DerivedData through SYMROOT/OBJROOT, and usage errors arexcodebuild_usage. SimulatorKit is found inContents/SharedFrameworks, and a WDA fallback says why HID wasn't used. Launches are watched for 8s (XCFORGE_LAUNCH_WATCH_SECONDS) and wait up to 15s for the report (XCFORGE_CRASH_REPORT_WAIT_SECONDS); an exit without one says when it died and where to look.--projectis made absolute. The first WDA start gets 15 minutes to build and 180s to answer (XCFORGE_WDA_START_SECONDS), concurrent callers wait for it, and failures name the cause (such as an old Homebrew xcforgeWDA copy). Queued processes are left out of holders; timeouts aren't counted as failed tests;--jsonerrors are a JSON envelope on stdout; unreported snapshots are deleted and xcforge's artifacts older than two days are pruned; landscape screenshots report landscape points; everyuicommand takes--simulatorandset_orientationtakessimulator. The snapshot worktree stays registered on purpose so snapshot builds are incremental; the docs say how to remove it.Why
Bundles 6-12 of the dev loop plan (https://claude.ai/artifact/FKqRxvqGuk9xsYe11mpUEN and the #5 thread's audit): stranded calls, orphaned xcodebuild, build/test output Shuttercoach agents worked around with raw xcodebuild and xcresulttool, launches that hid crashes, UI calls that hit the wrong screen, slow inner loops, and a tool list too large and inconsistent for agents to use well. Justin's decisions applied: old tool names stay as aliases for one release, slim JSON is the MCP default (text stays for the CLI), and diagnose tools are in an off-by-default group.
How
Bundle 7:
ProcessTree,ChildProcesses+ signal handling inMain,ProgressHeartbeat,XcodebuildQueue, per-project yaml inSessionState.resolveProject,ToolRegistry.unknownArgumentsError.Bundle 8:
TestIDs,FailureMessage,TestFailureText;SchemeFileandTestPlanInspector.testTargetNames;TestRunOptions; xcresult node-tree parsing;LastFailuresStorerun settings.Bundle 6:
TestTools.compileFlags/compileSettings;runBuildForTesting(testplan:targets:);AutoDetect.pickSimulator;HangWatchdog.defaultSampleAt;executeListTests(withoutBuilding:).Bundle 9:
AppLivenessandCrashReports(pid fromsimctl launch, newest.ipssince launch);DetachedCapture(nohup'd capture writing to~/.xcforge/capture);BuildTools.appProduct(fromSettings:)from-showBuildSettings -json;LogCapture.position.Bundle 10:
WDAPorts;UITarget(addssimulatorto UI tool schemas, selects the WDA before dispatch) andUIWait(adds and runs the waits), both applied inProviderRegistry;WDAClient.sessionCapabilities,startRunnerwithTEST_RUNNER_USE_PORT;UITools.axpTargetsSimulator;flattenWDASourcepruning;ScreenshotShaping+ScreenInfoCache;SimSetupTools.swift;IndigoHIDClient.normalize(…orientation:).Bundle 11:
BuildTypecheck,BuildSnapshot(git worktree + path mapping),LSPSetup,XcodebuildOptions.jobs/allErrors, SwiftPM package discovery and output parsing inSwiftPackageProvider.Bundle 12:
ToolSurface(group specs, camelCase listing and argument normalising, deprecated aliases, read-only annotations),UITap,BuildAgentResult,PlanSessionFile,XCForgeVersion;ToolRegistry.declaredTool/onToolListChanged;LastResultStore.canonicalProject+flock.Real-Mac fixes:
DeviceTools.physicalEntry/isSimulatorEntry/connectionState;BuildTools.targetBuildRoots/xcodebuildUsageError;IndigoHIDClient.simulatorKitCandidates;AppLiveness.checkpolling withafterSeconds;SessionState.absolutePath;WDAClient.explainWDABuildFailure,runnerStartSeconds, deploy waiting;BuildLock.externalHolders;ArtifactPruning;ScreenshotShaping.orientedPointSize;UISimulatorTargetonuicommands;XCForgeCLI.runCLI+printJSONError. Tests inDeviceSupportTestsandMacVerifyFixesTests.Testing
swift testpasses (CI)swift build -c releasecompiles (CI)AgentCallSafetyTests,TestResultsTests,BuildLoopTests,RunLoopTests,UIDrivingTests,ToolSurfaceTests,DeviceSupportTestswith devicectl shapes captured from Xcode 27.2,MacVerifyFixesTests); a test checks the documented tool count and that every listed tool is named in the referencesshared-mac.md,test-tools.md,build-tools.md,spm-tools.md,simulator-tools.md,log-console.md,ui-automation.md,screenshot-visual.md,device-tools.md,plan-execution.md,auto-detection.md,cli-commands.md, newtool-surface.md)Verified on a real Mac (Xcode 27.2, ShutterCoach):
device listshows only the phone,27.2 [localNetwork, connected], no simulatorslockfholder shows inlock status, and builds queue behind it without overlapping--jobs 6(0 errors); no-op rebuild about 32s of xcodebuild time;build compilestraight aftertest runrecompiles nothingbuild typecheckon a target with its own schemenotifications/cancelledeach leave no xcodebuild behindtool_groupssendslist_changed(114 tools)lsp setupwith an absolute path gives working jump-to-definition into ShutterCoachShared--from-snapshotcompile with an absolute path reports warnings at the real repo pathslaunch_appargs and env reach the applog startkeeps capturing after the shell exitsFixed after that run, waiting on a re-check:
typecheck --targetwith a custom DerivedData, HID taps on Xcode 27 (and the landscape HID mapping), crashes 2-6s after launch with late.ipsfiles, relative--project, first WDA start, and the smaller items above.Still needs a real-Mac check:
wda starton a phone;simulator:hitting the same one;retries: 2on a flaky test lists it under Flaky;build-for-testing -only-testing:builds only the named target (ShutterCoach has one test target, so it couldn't tell);sim_localeandsim_privacytake effect after a relaunch;plan runthat pauses, thenplan decidein a new shell;build typecheckon a framework target with no scheme;--from-snapshoterror paths (only warnings were seen);forceAppLaunch: falseleaves the app's screen in place; WDA restart without a rebuild;🤖 Generated with Claude Code
https://claude.ai/code/session_01EtxtFPs6UBAWXqEpkj2W2N