Make waits settle instead of stalling, harden the iOS driver, and add the all: recording key - #228
Merged
Merged
Conversation
This was referenced Aug 10, 2026
Connecting a device could leave an MCP client (Claude Code, Cursor, Goose) believing the device has no tools at all. Asking for the tool list right after connecting returned only the session tools — no tap, no swipe, no inputText — and the client had no reason to ask again. Connecting a device kicks off a tool-list refresh that finishes after the connect call has already returned, so "right after connecting" lands in the middle of it. That refresh used to remove every device tool up front and add the new ones back at the end, so anything asking during the window saw an empty surface. It now registers the new surface first and removes only what the new surface no longer has, which is what the host-tool half of the same method already did. Also stops the MockRpcServer test fixture from failing when its port is momentarily held by an unrelated socket: it waits for the port to be bindable before handing it to Ktor. The port is inside the OS ephemeral range, so a loopback connection elsewhere on a busy machine can own it — and letting Ktor take the BindException also surfaced on its own coroutine, failing whichever unrelated test ran next. Implementation detail: the swap is in TrailblazeMcpServer.registerToolsLocked; MCP's FeatureRegistry is keyed by tool name and addTool atomically replaces an existing entry, so re-registering in place never duplicates and never leaves a gap. MockRpcServer.awaitBindable is the new gate.
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.
Summary
Syncs the latest upstream changes through 2026.08.10.
Waiting on a loading screen now works the way you'd expect.
waitandwaitForChangeare settles, not timers — each returns the moment the UI goes quiet, and the duration you pass is only a ceiling. They used to report a duration they never actually spent. There's now a realsleeptool for the rare case you genuinely want wall-clock time to pass, and the agent's system prompt steers toward positive-signal waits (assert on the thing you're waiting for) instead of padding with timers.The iOS driver got a long hardening pass. Web page content inside a
WKWebViewnow reaches the accessibility tree, so trails can assert on it. Launches pre-grant app permissions so replays stay dialog-free. A failed state-clear can no longer leave the app uninstalled for the rest of the suite, recordings that omitstopAppcold-start the app again, off-screen elements fail asserts and taps loudly instead of silently, placeholder text is findable on iOS 18, and coordinates clamp to bounds. Every action in an IOS_AXE run now gets a screenshot in the report, and simulator video/screenshots record correctly even when the JVM temp dir sits on a non-boot volume.Declare a recording once for every device with
all:. A new universal classifier key resolves for every device at the lowest priority, so a cross-platform trail whose platforms genuinely share a trailhead or recording writes one block instead of byte-identicalandroid:andios:duplicates. Any explicitly-declared classifier still outranks it.Run reports are faster and cleaner. The run list paints instantly instead of blocking on parsing the whole report; the index, timeline, and video views got a polish pass; self-heal shows up when only the logs recorded it; and there's one report column per platform rather than one per CI shard's simulator.
trailblaze checkno longer passes when it never type-checked your recordings. A framework JAR built without its bundled TypeScript compiler used to skip trail-recording validation silently and still go green — that's now a hard build failure at package time and a fatal error at check time.Also in here: a recordable assertion for the number of matching elements, an unrecognized
--driverfailing loud with exit 3 instead of silently running the default, workspace-local toolsets being used when compiling and checking trailmaps, scripted-tool sources found in trailmaps shipped inside a jar, a record-time warning when a tap falls back to raw screen coordinates, and the default per-objective agent-loop cap cut from 50 to 25.Notes for reviewers
gemma-4model id corrected in the generated doc. add gemma4 as a model for google #225 addedgemma-4-31b-ittogoogle.yamlbut hand-wrote theLLM_MODELS.mdrow asgemma-4-26b-a4b-it.LLM_MODELS.mdis generated fromgoogle.yaml, so regenerating it produces the row matching the source. Flagging it since it changes a row that landed in add gemma4 as a model for google #225.Carries Always await on-device completion for MCP tool dispatch #221's on-device MCP dispatch fix forward unchanged; the surrounding KDoc is rewritten and
screenState()now reuses the already-built device manager rather than constructing a second one.Second commit: a tool-list fix this sync's CI surfaced. Connecting a device kicks off a tool-list refresh that finishes after the connect call returns, and that refresh removed every device tool up front before adding the new ones back — so an MCP client asking for tools right after connecting could see only the session tools and conclude the device had none. It now registers the new surface first and removes only what the new surface no longer has (the shape the host-tool half of the same method already used). Whether you hit the window was pure timing; this branch's connect got fast enough to lose the race, which is what
android-tests-mcp-dispatchwas reporting. Same commit also makes theMockRpcServertest fixture wait for its port to be bindable before handing it to Ktor — the port sits inside the OS ephemeral range, an unrelated loopback socket can hold it, and letting Ktor take theBindExceptionalso failed whichever unrelated test ran next. Landing in the internal repo in parallel.