fix(browser): downloads complete over CDP connections#89416
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 5, 2026, 9:22 PM ET / 01:22 UTC. Summary PR surface: Source +167, Tests +347, Docs +1. Total +515 across 13 files. Reproducibility: yes. source-reproducible: current main's navigate helper is URL-only and rethrows Playwright's Review metrics: 2 noteworthy metrics.
Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review detailsBest possible solution: Land this PR or an equivalent browser-plugin fix after maintainer security-boundary review, preserving policy validation before Do we have a high-confidence way to reproduce the issue? Yes, source-reproducible: current main's navigate helper is URL-only and rethrows Playwright's Is this the best way to solve the issue? Yes. The PR fixes the owner seam around Playwright AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 3c2316c43cd2. Label changesLabel justifications:
Evidence reviewedPR surface: Source +167, Tests +347, Docs +1. Total +515 across 13 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (1 earlier review cycle)
|
b17a21c to
286a934
Compare
286a934 to
0b7d589
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
dc77042 to
e7af667
Compare
e7af667 to
c8fc83c
Compare
|
Maintainer verification for exact head What changed from the submitted fix:
Automated proof:
Real Gateway/browser-agent proof:
This is the best fix: one maintained capture/persistence path, useful attachment navigation, and no weakening of the network-to-file trust boundary. Ready to land. Thank you, @zhangguiping-xydt. |
|
Merged via squash.
|
* fix(browser): surface navigate downloads in CDP mode * Validate navigation downloads before saving * fix(browser): observe navigation download capture timeouts * refactor(browser): unify managed download capture * test(browser): satisfy download fixture lint --------- Co-authored-by: Peter Steinberger <[email protected]>
* fix(browser): surface navigate downloads in CDP mode * Validate navigation downloads before saving * fix(browser): observe navigation download capture timeouts * refactor(browser): unify managed download capture * test(browser): satisfy download fixture lint --------- Co-authored-by: Peter Steinberger <[email protected]>
Summary
/navigateattachment responses in Playwright/CDP mode now complete as successful downloads instead of surfacing the navigation abort as a tool failure or dropping the file silently.{ url, download }; rejected captured download URLs fail beforesaveAswrites managed bytes./navigateseam, managed download capture, and focused test-harness support, with RED/GREEN coverage for the ClawSweeper security-boundary finding./navigatepath.downloadresult, does not change config, schema, protocol, migration, default behavior, allowlist behavior, or unrelated browser tools.beforeSaveordering between captured download metadata,assertBrowserNavigationResultAllowed, andsaveAs.Linked context
Closes #48045.
Related: ClawSweeper review on this PR requested blocked attachment-download persistence ordering coverage and targeted validation.
This was requested by ClawSweeper through the
status: waiting on authorreview signal.Root Cause
/navigateowned the Playwrightpage.goto()lifecycle, but attachment downloads caused Playwright to abort navigation withDownload is startingor URL-matchednet::ERR_ABORTEDbeforenavigateViaPlaywrightcould return useful download metadata.assertBrowserNavigationResultAllowed, so a policy-denied attachment could leave rejected bytes in managed storage.page.goto()settled, but if the capture timeout rejected first, the rejected promise was left unobserved and surfaced as an unhandled rejection.createManagedDownloadCaptureForPagenow accepts abeforeSavehook, builds URL/filename candidate metadata, runs the existing navigation result policy check first, and only callssaveAsafter policy approval. The/navigatepath passes that hook at the point where it arms the download capture, so allowed downloads still succeed and blocked downloads reject before persistence instead of being cleaned up after the fact. The/navigatepath now also observes the armed capture promise immediately, so later download-starting handling still awaits real captured downloads while ordinary non-download success/error paths cannot leave a timed-out waiter rejection unhandled.Real behavior proof (required for external PRs)
/navigateto aContent-Disposition: attachmentURL in Playwright/CDP mode returns success download metadata and writes the file instead of failing the tool call.Content-Disposition: attachment; filename=proof.txt.pnpm exec tsx cdp-navigate-download-proof.tswas run from the task worktree against the patchednavigateViaPlaywrightpath.navigateViaPlaywright, receiveddownload.triggered: trueandsuggestedFilename: proof.txt, wrote the managed download bytes, and read back the saved file content.openclaw cdp navigate download proof\n, proving the attachment navigation returned success metadata and wrote the downloaded bytes in a real CDP browser session.crabboxbinary failed its basic--version/--helpsanity checks before project checks started.Download is startingor URL-matchednet::ERR_ABORTED, so/navigatereturned an error or no download metadata.Proof continuity after branch update
dc7704290e7307b9364b870a7144f8991142a3f4is a merge commit with parents0b7d58903f5e5c0d63f96a4cb847602b620c98f6(the submitted browser repair/proof head) ande209a56d0b61e5d6c0b5d53b760e813d9849f85b(currentmain)./navigatedownload fix, the blocked-download persistence regression, or the capture-timeout unhandled-rejection regression./navigatepath because it was run against the submitted repair head and the branch update only merged latestmainon top of that repair. The latest main-side update also cleared the unrelatedsrc/agents/model-picker-visibility.tsunused-import blocker that previously prevented a second local submit.proof: sufficientwas removed after the head changed, but the PR body still carries the real behavior proof and this section ties that proof to the current Update branch head for the next ClawSweeper/maintainer pass.Tests and validation
Commands run after the latest patch:
Regression coverage added or updated:
extensions/browser/src/browser/pw-session.test.tsplus the existing/navigateguard filesextensions/browser/src/browser/pw-tools-core.snapshot.navigate-guard.test.tsandextensions/browser/src/browser/pw-tools-core.waits-next-download-saves-it.test.ts./navigateregression covers a capture timeout winning before ordinary non-download navigation settles, proving the timed-out capture promise is observed rather than reported as an unhandled rejection.saveAsis not called, which directly protects the security-boundary invariant that rejected download bytes cannot enter managed storage without adding a broad end-to-end fixture.extensions/browser/src/browser/pw-session.test.tsaddsvalidates captured navigation downloads before saving managed bytes, which verifies the capture's policy hook runs beforesaveAsand rejected downloads do not persist managed bytes.extensions/browser/src/browser/pw-tools-core.test-harness.tsmirrors the newbeforeSavesemantics so existing/navigatetests exercise the same ordering.What failed before this fix:
Passing evidence after fix:
Changed-check evidence:
Regression Test Plan
extensions/browser/src/browser/pw-session.test.ts/validates captured navigation downloads before saving managed bytes.saveAsis not called, proving rejected bytes cannot enter managed storage.extensions/browser/src/browser/pw-tools-core.snapshot.navigate-guard.test.ts,extensions/browser/src/browser/pw-session.test.ts, andextensions/browser/src/browser/pw-tools-core.waits-next-download-saves-it.test.tscover allowed metadata, URL-matchedERR_ABORTED, no-capture rethrow, save failure propagation, and existing route response behavior.Risk checklist
Did user-visible behavior change? (
Yes/No)Yes. Browser
/navigatecan now return download metadata for attachment responses instead of surfacing the Playwright navigation abort.Did config, environment, or migration behavior change? (
Yes/No)No.
Did security, auth, secrets, network, or tool execution behavior change? (
Yes/No)Yes. This touches browser navigation/download security ordering by ensuring captured download URLs pass the existing navigation policy before managed bytes are saved.
What is the highest-risk area?
Risk labels considered: browser, download-handling, SSRF policy, security-boundary, and the large-diff warning caused mostly by regression/test-harness coverage. The highest risk is weakening the browser SSRF/download boundary by treating a blocked or unrelated aborted navigation as a successful managed download, or by persisting rejected attachment bytes before policy validation.
How is that risk mitigated?
The patch only handles Playwright download-starting errors or URL-matched
ERR_ABORTED, requires an armed captured download before returning success, validates the captured download URL through the existing browser navigation policy beforesaveAs, closes blocked navigation targets on policy denial, and adds a regression proving blocked downloads do not callsaveAs. The production scope is limited topw-session.tsandpw-tools-core.snapshot.ts; the rest of the line count is targeted browser regression coverage and harness support.Current review state
Next action: maintainer/ClawSweeper review of the updated branch after this author follow-up.
Still waiting on author, maintainer, CI, or external proof:
checks-node-agentic-commands-agent-channelfailure was on the old head286a934bcf5115f52f32037e8ddc95a2ffcf0eaf; this branch has been synced onto current main again after the local repair and needs remote CI to rerun on the pushed head.Bot or reviewer comments addressed:
status: waiting on author: addressed by this follow-up update; label removal is maintainer/bot-owned after review.page.goto()settles: addressed byhandles capture timeouts that win before ordinary navigation settles.extensions/browser/src/browser/pw-session.test.tsregression that fails before the repair and passes after it.saveAsand by the no-saveAsregression./navigatepolicy wiring.extensions/browser/src/browser/pw-tools-core.snapshot.ts: addressed by passingbeforeSaveintocreateManagedDownloadCaptureForPagefromnavigateViaPlaywright.node scripts/run-vitest.mjs extensions/browser/src/browser/pw-tools-core.snapshot.navigate-guard.test.ts extensions/browser/src/browser/pw-session.test.ts extensions/browser/src/browser/pw-tools-core.waits-next-download-saves-it.test.tspassed.node scripts/run-vitest.mjs extensions/browser/src/browser/pw-tools-core.snapshot.navigate-guard.test.ts extensions/browser/src/browser/pw-session.test.ts extensions/browser/src/browser/pw-tools-core.waits-next-download-saves-it.test.tspassed.node scripts/run-vitest.mjs extensions/browser/src/browser/pw-session.test.ts -t "validates captured navigation downloads before saving managed bytes"passed.git diff --check origin/main..HEAD: addressed; command passed with exit code 0.OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 CI=1 PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false ... check:changed --timed: addressed; command passed with typecheck, lint, and guard summary all ok.