Skip to content

fix(ios): serve regular --depth from every snapshot backend - #2431

Merged
thymikee merged 2 commits into
mainfrom
claude/agent-device-issue-2403-c5c003
Sep 10, 2026
Merged

fix(ios): serve regular --depth from every snapshot backend#2431
thymikee merged 2 commits into
mainfrom
claude/agent-device-issue-2403-c5c003

Conversation

@thymikee

@thymikee thymikee commented Sep 9, 2026

Copy link
Copy Markdown
Member

Summary

Closes #2403.

On 0.21.0 every is <selector> visible --depth <n> on the reporter's iOS Simulator failed with regular iOS snapshot presentation requires a valid viewport. The cause is in the runner, not the daemon: SnapshotBackendKind.canServeRegularPresentedDepth (from #1947) refused a regular depth-capped request on private AX at any depth and on the query sweep past depth 1. Whenever the plan is pinned or deferred to private AX (custom actions, a private AX verdict already on the session, the XCTest channel penalty), no backend could serve, the plan fell through to its synthetic sparse root (one Application node, zero rect), and the daemon rejected that root as a missing viewport.

Presentation already applies the presented-depth cut to whatever hierarchy a backend acquired, and a depth-capped regular capture is a subset of the unscoped one from the same backend, so the refusal protected nothing that truncated/effectiveDepth did not already disclose. This PR deletes the gate, declares private AX as regular-depth=presentation-cut (fixture, TS registry, Swift enum, all parity-tested), rewrites the ADR 0004 paragraph that made the old claim, and adds a presentation test for the cut on a hierarchy acquired past the frontier.

agent-device snapshot --depth 20 --actions   # private AX pinned: 58 nodes, was COMMAND_FAILED
agent-device is visible "role=button label=General" --depth 20

8 files touched. Not changed here: a genuine sparse runner verdict (no backend could read the screen) still surfaces as the viewport-invariant error instead of the runner's own "use screenshot" message; that is a separate daemon-side seam.

Validation

Tested commit: bb3dfdc8a2b8627d5f16adb423c4a18ddef1af50.

  • Repro before the fix on a local iOS 26.2 Simulator (Settings): snapshot --depth 20 --actions returned the runner's sparse payload (state: sparse, one zero-rect Application node) and the daemon failed with the reported message; plain --actions worked.
  • After the fix, same device: snapshot --depth 20 --actions returns 58 nodes; --depth 1 --actions returns 4. Through the test-owned snapshotPreferredBackend: 'private-ax' transport seam: snapshot --depth 20 is a 58-node recovered private AX capture, is visible role=button label=General passes at depth 20 and 3, and depth 0 returns the correct Selector did not match.
  • New runner-path regression testPrivateAXPinnedRegularDepthReachesAcquisitionAndPresentation (PR iOS lane): green at 1db684c in 4.9 s; red with the gate restored (5 assertions, runner logs SNAPSHOT_BACKEND_DEPTH_UNSUPPORTED backend=private-ax depth=1). Evidence in the review reply.
  • Runner unit tests (AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS=1, iOS 26.2 sim): testSnapshotBackendDeclarationsMatchCapabilityFixture, testSparsePayloadReasonMatrix, testLegacyQualityMessageStatesFallbackMeaning pass (3 executed).
  • swift test --package-path apple/snapshot-presentation: 3 tests pass, including the new RegularDepthTests.
  • pnpm check:affected --run: green on bb3dfdc (387 files, 2827 tests). On 1db684c the vitest-related stage hit 7 contention timeouts on a Mac at load 125; those five files pass in isolation (68/68) and the delta is Swift test code plus one workflow line; check:xctest-selection passes.

Risk: the physical-device plan now also lets the query sweep answer a --depth ≥ 2 request with its flat sweep (same payload it already returned unscoped) instead of no answer.

A regular depth-capped request was refused on every runner backend but the
recursive tree: the query sweep past depth 1 and private AX at any depth
returned no capture, so a plan pinned or deferred to private AX (custom
actions, a private AX verdict on the session, the XCTest channel penalty)
fell through to the synthetic sparse root, which the daemon then rejected as
"regular iOS snapshot presentation requires a valid viewport".

Presentation already applies the presented-depth cut to whatever hierarchy a
backend acquired, and a depth-capped regular capture is a subset of the
unscoped one from the same backend, so the refusal protected nothing the
unscoped answer did not already disclose through truncated/effectiveDepth.
Delete the gate, declare private AX as regular-depth=presentation-cut, and
record the rule in ADR 0004.

Closes #2403
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.53 MB 4.53 MB -486 B
Package (unpacked) 4.53 MB 4.53 MB -486 B
Package (download) 1.34 MB 1.34 MB -113 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 31.9 ms 29.5 ms -2.3 ms
CLI --help 83.7 ms 82.0 ms -1.8 ms

@thymikee

Copy link
Copy Markdown
Member Author

The fix looks sound at bb3dfdc, and the reported simulator reproduction plus CI are green. One regression test is still needed: RegularDepthTests calls presentation directly, so it passes even with the old runner backend refusal restored. Add a runner-path test for private AX pinned with regular --depth that reaches acquisition and presentation rather than the sparse fallback, and show that restoring the deleted gate makes it fail.

…ugh acquisition

The presentation-package test passes with the old backend depth gate restored,
because it calls presentation directly. This runner-bundle test pins private
AX, asks for regular depth 1 against the launched host app, and requires the
plan to reach acquisition and presentation: a private-ax verdict that is not
sparse, more than one node, a real root rect, and a payload no larger than the
unscoped capture from the same backend. With the gate restored the plan logs
SNAPSHOT_BACKEND_DEPTH_UNSUPPORTED and returns the zero-rect sparse root, and
the test fails.
@thymikee

Copy link
Copy Markdown
Member Author

Added in 1db684c: testPrivateAXPinnedRegularDepthReachesAcquisitionAndPresentation (runner bundle, os(iOS), registered in the ios.yml PR lane; check:xctest-selection now reads 237 declared / PR list 89 / 0 unreachable).

It launches the host app with --agent-device-selector-read-regression, pins preferredBackend: private-ax, runs runSnapshotCapturePlan(regularVisiblePlan, depth: 1), and requires a private-ax verdict that is not sparse, more than one node, max presented depth 1, a non-zero root rect, the Readable target button, and a payload no larger than the unscoped capture from the same backend.

Green at 1db684c (iOS 26.2 Simulator, AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS=1): passed in 4.9 s; runner log shows PRIVATE_AX_SNAPSHOT_USED nodes=9 depth=64 followed by SNAPSHOT_RECOVERED backend=private-ax for both captures.

Red with the gate restored (git apply -R of the bb3dfdc gate hunks, rebuilt, same test): failed in 4.0 s on five assertions — runner log SNAPSHOT_BACKEND_DEPTH_UNSUPPORTED backend=private-ax depth=1, then

XCTAssertNotEqual failed: ("sparse") is equal to ("sparse")
XCTAssertGreaterThan failed: ("1") is not greater than ("1")
XCTAssertEqual failed: ("Optional(0)") is not equal to ("Optional(1)")   // max depth
XCTAssertNotEqual failed: ("SnapshotRect(x: 0.0, y: 0.0, width: 0.0, height: 0.0)") is equal to (...)
XCTAssertTrue failed                                                       // "Readable target" absent

i.e. exactly the synthetic sparse root the daemon then rejects as a missing viewport.

pnpm check:affected --run on 1db684c: the vitest-related stage hit 7 timeouts (/bin/ps timed out after 1000ms, Test timed out in 5000ms) while this Mac was at load 125 with two other worktrees building; the same five files pass in isolation (68/68), and the full gate was green on bb3dfdc, whose only delta is this Swift test plus the workflow line.

@thymikee

Copy link
Copy Markdown
Member Author

The runner-path regression at 1db684c addresses the remaining review gap. It exercises private AX acquisition and presentation, and the reported simulator red/green run shows that restoring the old gate produces the sparse-root failure. No remaining code findings; ready for human review while CI finishes.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 10, 2026
@thymikee
thymikee merged commit bd42b26 into main Sep 10, 2026
17 of 18 checks passed
@thymikee
thymikee deleted the claude/agent-device-issue-2403-c5c003 branch September 10, 2026 09:21
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-10 09:21 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0.21.0: --depth on the iOS Simulator returns COMMAND_FAILED "regular iOS snapshot presentation requires a valid viewport"

1 participant