Files
Michał Pierzchała 83d54614d8 fix: bound iOS capture stalls and make runner recovery session-preserving (#1105) (#1107)
* fix: bound iOS capture stalls and make runner recovery session-preserving (#1105)

Runner (Swift):
- Coalesce duplicate transport sends of one commandId onto the in-flight
  execution instead of enqueueing them again behind it (capture pileup).
- Fail fast with RUNNER_BUSY while watchdog-abandoned main-thread work is
  draining; escalate to RUNNER_WEDGED past 120s so the daemon recycles.
- Carry the capture-plan deadline into the query-sweep and private-AX
  ladder tiers so chained recovery cannot stack past the watchdog.
- Penalize the tree backend after a slow (>5s) or abandoned capture and
  lead subsequent regular plans with private-AX for that bundle (sticky,
  120s), stamped recovered/budget so the deferral stays observable.

Daemon (TS):
- Per-request runner recycle budget: at most one invalidate+reboot per
  request, then fail fast with an actionable, session-preserving hint.
- RUNNER_WEDGED joins the runner-fatal invalidation reasons.
- Interaction commands (click/fill/longpress/press/type/get/is) preserve
  the daemon on request timeout like snapshot/wait/find: resetting it
  destroyed every healthy app session the daemon owned.

* fix: suppress AX-broken-screen snapshot issues so the runner survives capture

XCTest records 'Failed to get matching snapshot: kAXErrorIllegalArgument'
issues for every XCUIApplication query on AX-broken screens; after a few
of them the test case tears down the moment the in-flight command
completes, killing the long-lived runner after every capture of the
screen (the restart loop behind #1105). The capture plan already
classifies and recovers from AX failures, so this issue class is noise:
swallow exactly it in record(_:); everything else still records and
still drives XCTEST_RECORDED_FAILURE.

* feat: time-slice the XCTest tree capture on a worker thread

The tree snapshot XPC is a single blocking call whose duration moves
with live content (4s to minutes on Bluesky profile screens); no
in-process budget could bound it on the main thread. Run it on a worker
bounded to an 8s slice: on timeout the plan penalizes the tree backend,
skips the XCTest-backed tiers while the abandoned XPC drains (they
would block behind it inside testmanagerd), and recovers through the
private AX backend, which does not use testmanagerd.

* tune: lower the tree-backend penalty threshold to 3s

The Bluesky profile tree grind measures ~4.5s before kAXErrorIllegalArgument,
just under the old 5s threshold, so every capture re-paid the doomed grind
(9s each). At 3s the second capture onward defers to private AX (2.4s
snapshot, 4.9s press on the live repro).

* fix: harden the AX-issue suppression per review

- Require the kAXError token: 'Failed to get matching snapshot: Timed out
  while evaluating UI query.' is a genuinely-hung-query signal and must
  keep recording (and keep driving XCTEST_RECORDED_FAILURE). Sibling AX
  server codes (kAXErrorCannotComplete, ...) are deliberately included:
  any AX-server rejection inside a matching-snapshot fetch is the same
  capture-plan noise.
- State honestly that the override is suite-global and why (tap-triggered
  queries record the same noise; command outcomes stay honest via their
  own error paths).
- Lock-guarded suppressed-issue counter following the file's existing
  abandoned-work counter pattern, logged with each suppression.
- Unit-test the pure classifier (record(_:) itself is not invoked: the
  must-record variants would record real failures in the test run).
2026-07-05 10:08:15 +02:00
..