claude-in-chrome: an UNGRANTED host makes executeScript-based tools hang their full timeout and blame the page, instead of denying
Summary
An executeScript-based browser tool called on a host the extension has not been granted does not return a permission error. It hangs for its full timeout and reports that the page is busy or still loading:
Failed to extract page text: Page still loading
(executeScript waited 45000ms for document_idle).
Error capturing screenshot: Script injection timed out after 5000ms —
the page is busy or mid-navigation ...
Every clause of that is false. The page is readyState: "complete", the tab is visibilityState: "visible", the connection is healthy, and javascript_tool answers instantly on the same tab in the same browser_batch.
The message misdirects to three things that are all fine — the page, the connection, and the browser selection — and never mentions permissions. Cost here: two separate multi-hour investigations across two days, both of which concluded "the Chrome connection is unstable."
Reproduction
Grant the extension a host (e.g. www.google.com) and leave another ungranted (e.g. example.com). Then, in one tab:
| host | granted | javascript_tool | get_page_text | computer screenshot |
|---|---|---|---|---|
| www.google.com | yes | works | works | works |
| example.com | no | works | 45 000 ms timeout | 5 000 ms timeout |
Steps:
tabs_context_mcp { createIfEmpty: true }navigateto the ungranted host.javascript_tool→ succeeds, returns the realdocument.title,readyState: "complete",visibilityState: "visible".get_page_texton the same tab → hangs 45 s, then reports the page is still loading.navigatethe same tab to the granted host →get_page_textand screenshot both succeed immediately.- Control: navigate the same tab back to the ungranted host → fails again immediately.
Step 6 is what makes this a permission fault rather than a transient: the only variable is the host.
What appears to be happening
There seem to be (at least) three injection paths that fail differently on an ungranted host:
| path | tools | on an ungranted host |
|---|---|---|
| immediate evaluation | javascript_tool, navigate, chrome.tabs reads | succeeds |
| executeScript at document_idle | get_page_text, read_page, find, computer screenshot | hangs to timeout |
| up-front permission check | computer click | denies immediately, by name |
A computer click on an ungranted domain returns Permission denied for this action on this domain — immediate and explicit. So the extension can produce a correct denial; the executeScript path just doesn't consult the grant before waiting.
A related contrast, same setup: javascript_tool is denied immediately and by name on some granted hosts (Permission denied for JavaScript execution on this domain). So javascript_tool and executeScript consult different policies, and only one of them reports failure honestly.
Why this is worth fixing beyond the message
Chrome resets runtime_granted_permissions whenever its HMAC over extensions.settings fails to verify — a hard kill of Chrome can cause it. So grants can disappear with nobody having touched them, and the symptom is not "permission denied", it is every page-read and screenshot timing out while navigation keeps working. That reads exactly like a flaky connection.
Inside browser_batch it is worse: a 45 s hang blows the tool's response budget, so the batch returns browser_batch did not respond in time and the remaining actions are discarded — a third distinct message for the same root cause.
Expected behaviour
get_page_text, read_page, find and computer screenshot should check the host grant before waiting for document_idle, and return the same immediate, named error that computer click already returns — ideally naming the host and pointing at the extension's site-access UI.
Failing that, the timeout message should not assert that the page is loading when the extension has not established that it can inject at all.
Environment
- macOS 26.6.2 (Apple Silicon)
- Google Chrome 152.0.7977.83
- Claude extension 1.0.91
- Claude Code 2.1.267
- One Chrome install, one profile, extension present in that profile only