fix: handle detached frames during snapshots - #2606
Conversation
|
There was a problem hiding this comment.
cubic analysis
All reported issues were addressed across 10 files
Linked issue analysis
Linked issue: STG-2781: Harden snapshots against detached iframe races
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | Prevent a detached same-process child frame from retrying an accessibility-tree request against the main CDP session (avoid duplicating the root snapshot). | The code classifies frame-scope errors and refuses unscoped fallback for stale child frames; a11yForFrame now checks allowUnscopedFrameFallback before retrying. Unit test covers the scenario. |
| ✅ | Permit unscoped Accessibility.getFullAXTree fallback only for the main frame and for live OOPIF session roots. | The allowUnscopedFrameFallback predicate is wired where per-frame AX is requested so only main frame or live OOPIF session roots allow unscoped fallback. |
| ✅ | Re-check child liveness before and after expensive CDP work and omit frames that detach during capture instead of merging partial/root-duplicated data. | collectPerFrameMaps repeatedly checks page.hasFrame and guards around doc-root resolution and after AX capture; detached frames are skipped rather than merged. Unit tests exercise detachment during capture. |
| ✅ | Merge only captured frames whose parent path was resolved successfully (avoid including frames without a valid parent prefix). | computeFramePrefixes and mergeFramesIntoSnapshot are updated to compute/limit mergeable frame ids and to filter per-frame outlines/maps by the included set before combining. |
| ✅ | Preserve live OOPIF behavior and propagate unrelated CDP failures (do not swallow non-frame-scope errors). | isFrameScopeError distinguishes frame-scope errors; unrelated CDP errors are rethrown so they propagate. Tests assert unrelated failure propagation. |
| ✅ | Add deterministic frame-race unit tests covering detached SPIFs, live/detached OOPIFs, main-frame fallback, unrelated errors, nested omission, and replacement frames. | A new comprehensive unit test file targets the listed race scenarios. |
| ✅ | Add a bounded real-browser iframe-churn integration test and include it in local integration groups. | A snapshot-iframe-churn integration test was added and the integration test groups were updated to include it; the PR describes multiple local runs and stability repetitions. |
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 7 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Summary
There is no public API or protocol change.
Why this is a Stagehand bug
The failure first appeared in a public OdysseysBench task after the run reached Hulu, but the same behavior reproduces on a local synthetic page with no third-party site, model, or external network dependency.
The failure sequence is internal to the V4 snapshot implementation:
captureHybridSnapshot()records the frame topology.Accessibility.getFullAXTree({ frameId })rejects the detached frame ID.Accessibility.getFullAXTree()without a frame ID.Each stale child can therefore duplicate root-sized work and data. The current
v4-spikebase no longer imposes the former implicit 10-secondpage.snapshot()client deadline, but the underlying correctness and work-amplification bug remains.Fix
The fix stays local to snapshot capture rather than changing the global unknown-frame session lookup contract. That keeps the blast radius narrow and preserves callers that may rely on the existing lookup behavior.
Resolved spike questions
Historical fit
This follows the repository's usual shape for lifecycle/CDP fixes: a narrow behavioral change, deterministic regression coverage, real-browser coverage where timing matters, and regenerated packaged assets. Comparable precedents include #2077, #1719, and the V4 snapshot packaging work in #2556.
Risk and limitations
E2E Test Matrix
pnpm exec vitest run packages/extension/understudy/a11y/snapshot/frame-races.test.ts --reporter=verbosepnpm run test:integration -- packages/sdk-ts/tests/integration/snapshotIframeChurn.test.tspnpm check && pnpm build && pnpm testpnpm exec vitest runuv --directory packages/sdk-python run --locked pytestfollowed by installed-wheelscripts/smoke.pypnpm --filter ./packages/extension build;go -C packages/sdk-go run ./internal/extensionpack --check