●FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtask●LIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its own●MCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS●PLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callback●SONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15●IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October●FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtask●LIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its own●MCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS●PLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callback●SONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15●IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Triaging iOS Test Failures Fast: Parsing .xcresult with Claude Code
Xcode 16 deprecated xcresulttool's legacy JSON, changing how you pull test failures out of a .xcresult bundle. Here's how to shape the new test-results format with jq and hand it to Claude Code to pinpoint the cause and a fix, drawn from running six apps in parallel.
When an iOS test fails in CI, the minutes I lose are rarely spent on why it failed. They go to finding where the reason is written down. Open the test navigator in Xcode and the failure is right there, but CI gives you a flood of log lines and a single .xcresult bundle that you can't read without waiting for Xcode to open it.
I've built wallpaper and relaxation apps for iOS and Android as a solo developer since 2014, and since May 2026 I've been migrating six of them to StoreKit 2 in parallel. Every migration knocks out a batch of purchase tests at once, and I once burned half a day opening .xcresult bundles one by one in Xcode just to see what broke. That extraction step — getting the contents of a failure out of the bundle — is exactly the part Claude Code can collapse into a single command.
The catch is that Xcode 16 deprecated the legacy JSON output of xcresulttool, so the xcresulttool get --format json that countless scripts and blog posts assumed no longer works cleanly. Below, I start from the newer test-results subcommands, pull out only the failed tests, hand them to Claude Code, and get back a cause hypothesis and a candidate fix.
What lives inside a .xcresult
A .xcresult is not a log file. It's a SQLite-backed bundle that packages test results, code coverage, attachments such as screenshots, and build diagnostics. Finder shows it as one file, but it's a directory you aren't meant to read directly. The supported way in is xcrun xcresulttool.
Here's the first trap. Through Xcode 15, the common approach fetched a root object and then walked IDs down to the test results.
# The old way, through Xcode 15 (now warns as deprecated in Xcode 16)xcrun xcresulttool get --path Result.xcresult --format json
This still runs in Xcode 16 for now, but without --legacy it warns that it's deprecated, and the output shape isn't guaranteed going forward. Worse, that root JSON is deeply nested: reaching the test list means pulling the id out of actions → actionResult → testsRef and calling get again. The scripts break easily, and it's far too heavy to feed to Claude Code as-is.
Old vs new, side by side
Xcode 16 added a test-results subcommand dedicated to test output, and that's the star here. No multi-step reference walking — you get a flat-ish JSON of the summary and the individual results directly.
# Before: fetch the root, then walk ids (two or three get calls)ROOT=$(xcrun xcresulttool get --legacy --path Result.xcresult --format json)TESTS_REF=$(echo "$ROOT" | jq -r '.actions._values[0].actionResult.testsRef.id._value')xcrun xcresulttool get --legacy --path Result.xcresult --id "$TESTS_REF" --format json# → still more nesting before you reach a single test's assertion text
# After: pull test results directly (Xcode 16+)# Summary: overall pass/fail, counts, durationxcrun xcresulttool get test-results summary --path Result.xcresult# Individual tests, including failure messages and file/line referencesxcrun xcresulttool get test-results tests --path Result.xcresult
The test-results tests output is a tree — test plan → bundle → suite → individual test — and each node carries a result (Passed, Failed, and so on). A failed node includes the assertion failure message and a source reference. The part where the old approach called get repeatedly now collapses into essentially one command.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Get the exact jq pipeline that flattens Xcode 16's new test-results output down to just the failed tests, so Claude Code reads a few hundred tokens instead of your entire test tree
✦Reuse a single slash command that turns failed test names, assertion messages, and file/line references into a cause hypothesis plus a candidate fix diff
✦See a Stop-hook and headless-CI setup that auto-triages whatever .xcresult is left behind, separating flakes from real bugs, based on migrating six wallpaper apps to StoreKit 2
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Claude Code can read long JSON, but handing it the whole tree spends tokens on tests that passed. I convert the result into an array of only the failures, narrowed to test name, failure message, and file/line, before handing it over. That saves tokens and, just as importantly, focuses Claude Code's attention on the part that actually matters.
This jq walks the test-results tests tree recursively and keeps only the Failed leaves.
#!/usr/bin/env bash# extract-failures.sh — emit only failed tests from a .xcresult as minimal JSONset -euo pipefailXCRESULT="${1:?usage: extract-failures.sh <path-to-.xcresult>}"xcrun xcresulttool get test-results tests --path "$XCRESULT" \ | jq ' # Flatten the test node tree recursively def leaves: if has("children") and (.children | length > 0) then .children[] | leaves else . end; [ .testNodes[]? | leaves ] | map(select(.result == "Failed")) | map({ name: .name, identifier: (.nodeIdentifier // .name), # Aggregate the failure detail nodes (message / source line) failures: [ (.children[]? | select(.nodeType == "Failure Message") | .name) ] }) '
The actual key names in test-results tests shift a little across Xcode point releases. On my machine (Xcode 16.x) the failure detail sat in a child node whose nodeType was Failure Message, but some setups put result at the top. So I run the raw output through jq 'keys' or jq '.testNodes[0]' first, look at the structure, then adjust the extraction. Hard-coding this means an empty array silently appears the day you update Xcode, and you won't notice.
The script output ends up as a short JSON like this:
[ { "name": "testPurchaseRestoresEntitlement()", "identifier": "StoreKitTests/testPurchaseRestoresEntitlement()", "failures": [ "XCTAssertEqual failed: (\"none\") is not equal to (\"pro\") - entitlement is not pro after restore (StoreKitTests.swift:84)" ] }]
At this size, what reaches Claude Code is a few hundred tokens. With 200 passing tests and 3 failures, only the 3 are sent.
A slash command that returns cause and fix
I hand the shaped failure JSON to Claude Code and ask for a cause hypothesis and a candidate fix. Typing the same instructions every time got old, so I keep it as a slash command under .claude/commands/ in the project.
---description: Analyze failed tests in a .xcresult and propose causes and fixesallowed-tools: Bash(./scripts/extract-failures.sh:*), Read, Grep---Analyze the most recent test failures.First, fetch the failures:!`./scripts/extract-failures.sh "$1"`For each failure in the JSON above, work through these in order.1. Summarize in one line what was expected vs what was observed2. Using the file/line, open the test and the implementation it exercises with Read3. Judge whether each failure is "likely a flake" or "likely a real bug" and state why4. Only for likely real bugs, propose a minimal fix as a diffLabel any uncertain guess as a "hypothesis" and do not assert it as fact.
$1 is the path to the .xcresult. Lines starting with ! tell Claude Code to run the command and embed its output in the prompt. Then /triage-tests ./build/Result.xcresult gives you the summary through the fix in one shot.
The part I care about most is forcing the flake-vs-real-bug split first. StoreKit tests occasionally fail on sandbox response latency, and chasing that as a real bug risks breaking code that was correct. Adding this judgment visibly cut the rework I did before applying a fix.
Auto-triage when CI fails
Locally you can run the slash command by hand, but when CI (Xcode Cloud or GitHub Actions) fails, nobody's there. I always pass -resultBundlePath to the CI test step so the .xcresult is reliably kept, and run a headless Claude Code triage only on failure.
# CI test run, with a fixed result-bundle pathxcodebuild test \ -scheme MyWallpaperApp \ -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \ -resultBundlePath "$PWD/build/Result.xcresult" \ | xcbeautify# Only triage if the test step just failedif [ "${PIPESTATUS[0]}" -ne 0 ]; then ./scripts/extract-failures.sh "$PWD/build/Result.xcresult" > build/failures.json claude -p "For each failure in build/failures.json, judge flake vs real bug and summarize" \ --allowedTools "Read,Grep" \ > build/triage.mdfi
claude -p is Claude Code's non-interactive (headless) mode. Drop the output into triage.md and you can attach it as a CI artifact or post it to Slack. In the morning, one notification tells you "two failures overnight: one flake, one real bug in restore."
To go further, a Claude Code Stop hook can analyze the .xcresult automatically whenever a local test agent stops. Hooks live in ~/.claude/settings.json or the project's .claude/settings.json.
This hook only extracts failures to a file on each stop. Stuffing heavy analysis into a hook slows the stop down, so I keep extraction in the hook and run the analysis when I actually want to read triage.md.
Where this trips you up, and how I avoid it
A few things that cost me time in real use — each one hard to see once you're stuck.
First, pointing -resultBundlePath at an existing path errors out. A .xcresult can't be overwritten, so a leftover at the same path makes xcodebuild fail. In CI I always rm -rf build/Result.xcresult before the test run. Locally, I've spun my wheels expecting a fresh result while the old one was still sitting there.
Second, running multiple -destinations in parallel bundles several test runs inside one .xcresult. test-results summary sums everything, but to know which simulator failed you have to read the device info on the tests nodes. In CI I pin a single destination first for reproducibility, then parallelize once it's stable.
Third, writing the jq against a hard-coded shape breaks silently on an Xcode point update. As above, looking at the raw JSON before writing the script is, in the end, the fastest path — that's my conclusion after many update cycles as a solo developer. I leave a comment at the top of extract-failures.sh reminding me that zero failures might mean "everything passed" or "the extraction broke."
Fourth, skipping token reduction lets attachment metadata leak into the JSON and inflate cost. A .xcresult can carry UI-test screenshots, and their references are nearly useless for failure analysis. Narrowing to failure-node text, as this shaping does, saves both tokens and attention.
One next step
Run xcrun xcresulttool get test-results tests --path <your.xcresult> once on a currently failing test and look at the structure yourself. Then tweak the key names in extract-failures.sh to match your Xcode, and failure analysis becomes a single command from that day on. Seeing the structure once is the insurance that lets you notice fast when a future update breaks it.
If you're running several apps solo too, I hope this saves you an afternoon. Thanks for reading.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.