Skip to content

fix(extension): file upload via file-chooser interception (#2108)#2110

Open
matthew6688 wants to merge 3 commits into
jackwener:mainfrom
matthew6688:fix/2108-file-chooser-interception
Open

fix(extension): file upload via file-chooser interception (#2108)#2110
matthew6688 wants to merge 3 commits into
jackwener:mainfrom
matthew6688:fix/2108-file-chooser-interception

Conversation

@matthew6688

Copy link
Copy Markdown

Fixes #2108.

Problem

opencli browser <session> upload, page.uploadFiles(), and page.setFileInput() fail on every site with {"code":-32000,"message":"Not allowed"} when running through the Browser Bridge extension. Root cause: since Chrome 72 (Chromium #928255), DOM.setFileInputFiles called directly with a nodeId is rejected for chrome.debugger-attached clients — it is only permitted as an answer to a file chooser Chrome itself opened. This breaks file/image attachment for all adapters (perplexity ask --attach, chatgpt image --image, project-file-add, gemini, google-ai, …).

Fix

extension/src/cdp.ts setFileInputFiles() now uses the file-chooser interception flow Chrome permits under chrome.debugger (same architecture as hangwin/mcp-chrome#145):

  1. DOM.enable + Page.enable
  2. Resolve the target input (DOM.querySelectorDOM.resolveNode, error contract unchanged)
  3. Register a one-shot chrome.debugger.onEvent listener for Page.fileChooserOpened (filtered by tabId)
  4. Page.setInterceptFileChooserDialog({enabled: true}) — suppresses the native dialog
  5. Click the input via Runtime.callFunctionOn with userGesture: true
  6. Take backendNodeId from the event and answer with DOM.setFileInputFiles({files, backendNodeId})
  7. finally: interception disabled, listener removed, object group released

Hardening beyond the happy path:

  • Per-tab in-flight guard — interception state is per-tab; overlapping calls would steal each other's fileChooserOpened event, so a second concurrent upload on the same tab is rejected with a clear error
  • Defensive interception reset at the start of each call (clears state a killed MV3 service worker may have left enabled)
  • mode: selectSingle + multiple files → explicit error
  • Missing backendNodeId in the event → explicit error (falling back to the queried nodeId would just hit the same -32000)
  • 10s chooser-event timeout with an actionable message

The CLI side is untouched: the direct-CDP path in base-page.ts setFileInputBySelector() still applies to non-extension backends, where the direct call is allowed.

Evidence

Unit: 5 new tests in extension/src/cdp.test.ts pinning the contract (event backendNodeId used — not the querySelector nodeId; intercept→click→setFiles ordering; userGesture: true; finally-disable on success/error/timeout; concurrency guard). Extension suite 23/23 green; npx tsc --noEmit clean at root. (The 6 src/cli.test.ts failures in npm test reproduce identically on main in my environment — unrelated.)

Live (macOS, Chrome for Testing 149, opencli CLI 1.8.6 daemon, extension loaded unpacked):

Test Extension Result
upload .txt to <input type=file> this branch (1.0.23) ✅ page receives file; change fires
upload PNG (121 KB) this branch ✅ page-side magic bytes 89504e470d0a1a0a, type=image/png, full size
upload 2 PDFs multi-select (128 KB + 1.6 MB, filename with spaces) this branch ✅ both received, magic %PDF, full sizes
control: same upload, same CLI/daemon v1.0.22 (store) {"code":-32000,"message":"Not allowed"}

Verification read the first 8 bytes of each File in-page (file.slice(0,8).arrayBuffer()) to confirm Chrome attached real file content from disk, not just metadata.

Extension version bumped to 1.0.23 (separate commit, matching prior bump convention); dist/ rebuilt from source.

🤖 Generated with Claude Code

matthew6688 and others added 3 commits July 9, 2026 15:59
…2108)

DOM.setFileInputFiles called directly with a nodeId is rejected with
-32000 "Not allowed" for chrome.debugger-attached clients (Chromium
#928255, since Chrome 72), breaking browser upload / page.uploadFiles()
on every site through the extension bridge.

Replace the direct call with the flow Chrome permits: enable
Page.setInterceptFileChooserDialog, click the resolved input with
userGesture, take the backendNodeId from the resulting
Page.fileChooserOpened event, and answer the chooser with
DOM.setFileInputFiles({files, backendNodeId}).

Hardening: per-tab in-flight guard (interception is per-tab, overlapping
calls would steal each other's event), defensive interception reset at
start, selectSingle vs multi-file check, remote object group released,
interception always disabled in finally.

Ref: codex round /tmp/codex-round-01-opencli-filechooser.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex audit follow-up: assert the fileChooserOpened listener is removed
on the timeout and missing-backendNodeId paths, not just on success.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

# [Bug] File upload (browser upload / page.uploadFiles) fails with -32000 "Not allowed" on all sites via the extension bridge

1 participant