Skip to content

feat(extension): debugger-free page control so Browser Hand works in Search - #33

Open
autohub-claude[bot] wants to merge 1 commit into
mainfrom
agent/kernel-worker/1c74b2b2
Open

autohub-claude[bot] wants to merge 1 commit into
mainfrom
agent/kernel-worker/1c74b2b2

Conversation

@autohub-claude

@autohub-claude autohub-claude Bot commented Nov 6, 2026

Copy link
Copy Markdown

Summary

Every Browser Hand page operation went through chrome.debugger. The "Search" browser (macOS, WebKit-based, Chrome-extension compatible) stubs chrome.debugger.attach/detach/sendCommand to refuse with debugger.attach isn't available in Search, so doctor reported path_b_required and named-tab creation, snapshot, click, fill, evaluate and screenshot all failed even though the relay and tab list worked. Sites also increasingly block sessions that have a debugger attached.

This makes a debugger-free transport the default and auto-detects a stubbed or missing chrome.debugger, so no debugging banner is shown and nothing depends on the debugging port.

Changes

  • Default transport is debugger-free (extension/services/TabManager.ts, CDPRouter.ts, StateManager.ts): Page.navigate → chrome.tabs.update (waits for load, reports loadComplete); Runtime.evaluate → chrome.scripting in the MAIN world; screenshots → captureVisibleTab. Snapshot, click, type and fill all ride on evaluate, so they work unchanged.
  • Auto-detect: probes chrome.debugger.attach({tabId: -1}) and treats "isn't available" as a stub; CDP is never attempted there, even if debugger mode is selected.
  • Strict-CSP pages (no unsafe-eval) fall back to chrome.userScripts.execute. The manifest now declares userScripts; without it the error says how to enable it.
  • Tab bootstrap / named tabs: tab grouping is cosmetic, so a missing chrome.tabGroups no longer fails Target.createTarget or group requests.
  • UI / CLI: popup "Page control" selector opts into the CDP transport; doctor reports transport {mode, effective, debuggerAvailable, debuggerUnavailableReason}; the CLI skips waiting for Page.loadEventFired when the navigate already settled (cli-js/src/tool.js).
  • Docs: skills/browser-hand/SKILL.md (new "Page control" section), references/troubleshooting.md (path_b_required), README.md.

Test plan

  • npm run build:relay — clean
  • Extension tests (cd extension && npx vitest run) — 71/71, includes new scripting-transport.test.ts
  • cli-js tests (npm test in cli-js/) — 162/162, includes new navigation-settled.test.js
  • npm run build:extension — builds; dist/chrome-mv3/manifest.json declares userScripts
  • npm run doctor against Search with this build — not run (see below)
  • Upstream path (daemon/cli) — not applicable, untouched
  • Manual verification on a Search-style surface — see below

Notes for reviewers

Not verified inside Search itself. The implementing run could not reload the extension in the real Search app (no CLI reload; driving its Settings UI was unsafe). Live checks ran in a disposable Chrome for Testing loading this build, with a copy of Search's chrome.debugger stub prepended (attach rejects with debugger.attach isn't available in Search, getTargets returns []) and tabGroups/userScripts removed. On that surface: doctor → tab_bootstrap_works with transport.effective: scripting; open --page-name created then reused; snapshot/fill/type/click/evaluate/goto succeeded on gym 01 with window.__oracle().ok === true; screenshot produced a real PNG; shadow-DOM (gym 08) and iframe (gym 09) snapshots listed all fields; the debugger opt-in path still passes in stock Chrome. Those live results come from the implementing run's report; the unit tests and builds above were re-run independently before opening this PR.

Unverified assumptions in Search's WebKit engine: tabs.captureVisibleTab (not referenced in Search's shim, so it must be native; screenshots depend on it), scripting.executeScript in the MAIN world, and userScripts.execute.

Verify in Search:

  1. After merge: npm install && cd extension && npm run build in the main checkout (Search installs from extension/dist/chrome-mv3).
  2. In Search → Settings → Extensions, reload Browser Hand; approve the new userScripts permission if prompted.
  3. npm run doctor → expect tab_bootstrap_works with transport.effective: scripting.
  4. open, snapshot, screenshot against extension/challenges/01-hello-form.html. If screenshot fails, captureVisibleTab is the first suspect.

Review focus

  • New required permission. userScripts is now declared in the manifest; existing installs may be asked to re-approve. On stock Chrome, CSP-strict pages also need "Allow User Scripts" on.
  • Focus policy. Screenshots of a non-active tab use the existing captureVisibleTab path in CDPRouter.captureScreenshot, which activates the tab inside its window and restores the previous one afterwards (no chrome.windows.update, no OS focus). That code predates this PR; it is now the default screenshot path in scripting mode rather than a fallback, so it runs far more often.
  • Known limits without the debugger: screenshot --full-page captures the viewport only; CSP-strict pages need userScripts; goto's response target.title can show the previous page's title.
  • npx tsc --noEmit in extension/ reports 3 errors, all in the untouched __tests__/CDPRouter.test.ts (loosely typed chrome.tabGroups mocks); none in files this PR changes.
  • Size: 832 changed lines (777 added, 55 removed), roughly 330 of them tests. One conceptual change, so not split.
  • Hosted CI here covers only the upstream daemon/cli jobs, so the extension and cli-js checks above are local-only.

Implementation harness: Claude Code (Sonnet 5.5) via the autohub-claude GitHub App.

🤖 Generated with Claude Code

…Search

Problem: every page operation went through chrome.debugger. Search (macOS,
WebKit-based, Chrome-extension compatible) stubs chrome.debugger.attach/detach/
sendCommand to refuse with "debugger.attach isn't available in Search", so
doctor reported path_b_required and named-tab creation, snapshot, click, fill,
evaluate and screenshot all failed, although the relay and tab list worked.
Sites also increasingly block sessions with a debugger attached.

Fix:
- Debugger-free transport is now the default. Page.navigate runs through
  chrome.tabs.update (waits for load, reports loadComplete), Runtime.evaluate
  through chrome.scripting MAIN world, screenshots through captureVisibleTab.
  Snapshot/click/type/fill all ride on evaluate, so they work unchanged.
- Auto-detect a stubbed or missing chrome.debugger (probe attach on tabId -1;
  match "isn't available") and never attempt CDP there, even in debugger mode.
- Strict-CSP pages (no unsafe-eval) fall back to chrome.userScripts.execute,
  which injects script text instead of eval'ing it; the manifest now declares
  userScripts. Without it the error says how to turn it on.
- Tab bootstrap: tab grouping is cosmetic, so a missing chrome.tabGroups no
  longer fails Target.createTarget or group requests.
- Popup "Page control" selector opts into the CDP transport; doctor reports
  transport {mode, effective, debuggerAvailable}.
- CLI skips waiting for Page.loadEventFired when the navigate already settled.

Expected improvement: doctor reaches tab_bootstrap_works in Search and the full
operation set works there, with no debugging banner for sites to detect.
Known limits without the debugger: screenshot --full-page is viewport only, and
CSP-strict pages need user scripts allowed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@autohub-claude
autohub-claude Bot requested a review from jack-arturo as a code owner November 6, 2026 05:19
@autohub-claude autohub-claude Bot added babysit:active Babysit agent is actively working this PR babysit:waiting-codex 👀 Codex reviewing current head and removed babysit:active Babysit agent is actively working this PR labels Nov 6, 2026
@jack-arturo

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T05:35:04.539658Z 3603bf6 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3603bf602b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

});
}, 150);
}
return { frameId, loaderId, ...(loaded ? { loadComplete: true } : {}) };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle navigation completion after the polling deadline

In the default scripting transport, a navigation that takes longer than 20 seconds exits the polling loop without loadComplete or errorText, and no subsequent completion event is emitted: the synthetic Page.loadEventFired is scheduled only inside if (loaded), while the background tab-update listener only re-registers the tab. Consequently, runCurrentOperation waits another full event timeout (30 seconds by default), even if the page finishes loading immediately afterward, then reports success even if it never finished. Return an explicit timeout error or continue observing the tab so a later completion can resolve the CLI's wait.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed against the code, and intentionally not changed in this PR because the fix needs a decision the PR does not specify.

What happens today: when navigateViaTabs reaches its 20s poll deadline with loaded === false, it returns no loadComplete and no errorText and emits no Page.loadEventFired. The CLI then sees navigationSettled() as false and waits the full event timeout before continuing. A page that finishes at second 21 therefore costs up to the extra wait instead of resolving on completion. (Reporting success after that timeout is pre-existing CLI behavior: tool.js already does waitForEvent("Page.loadEventFired", ...).catch(() => null) on the debugger transport too. What is new here is only that the scripting transport never emits the late event.)

No test or documented invariant covers the >20s path, so this is an unmade policy choice rather than a breach of an existing contract.

Decision needed (owner): for a navigation still loading after 20s in the scripting transport, should it

  • (A) keep observing the tab and emit the synthetic Page.loadEventFired when its status reaches complete (parity with CDP; no new failure mode), or
  • (B) return an explicit timeout errorText? This is a behavior change: for a freshly created tab open closes the target on any errorText (tool.js, the navError && createdTarget branch), so slow-loading pages would be torn down.

Leaving this thread open until that is decided.

@autohub-claude autohub-claude Bot added babysit:active Babysit agent is actively working this PR and removed babysit:waiting-codex 👀 Codex reviewing current head labels Nov 6, 2026
@autohub-claude

autohub-claude Bot commented Nov 6, 2026

Copy link
Copy Markdown
Author

Babysit stopped — scope. Two things need a human, and I did not decide either one.

  1. Open Codex thread (P2, scope) at extension/services/TabManager.ts:696: a navigation still loading after 20s in the scripting transport emits no completion event, so the CLI waits out its full event timeout. Fixing it means choosing between (A) keep observing the tab and emit the late Page.loadEventFired, or (B) return a timeout errorText, which makes open close a freshly created tab. The exact question is in the thread, which stays open. Codex found no P0/P1 issues.
  2. CI (ci, unrelated to this PR): Daemon (TypeScript + Tests) (ubuntu-latest) fails at playwright install chromium with Playwright does not support chromium on ubuntu26.04-x64; ubuntu-latest now resolves to an image the pinned Playwright cannot install onto. This PR changes nothing under daemon/, cli/ or .github/. feat(cli): add close to shut tabs Browser Hand opened #32 fails identically, and feat: target the connected browser, not Chrome by default (#29) #30 passes because it pins that matrix leg to ubuntu-24.04 in ci.yml. The Windows daemon leg was cancelled by matrix fail-fast, so there is no daemon test result for this head. That runner pin needs to land on main, then CI re-runs.

Verified head: 3603bf602be494347ebb90a632b413f0fb5b7e62. Codex reviewed this exact head (reviewed-commit marker, no reaction). Size: 832 changed lines (400-900 band). No automatic review fired inside the 10-minute grace window; the one baseline request was posted after it expired and the review began about 18 seconds later. If App-authored PRs should auto-review, check Codex settings for this repo (Auto code review). Local pre-PR review gate was skipped; the relay and extension builds, extension tests (71) and cli-js tests (162) pass locally. This change was not run inside Search itself; see the PR description.

@autohub-claude autohub-claude Bot added babysit:blocked Babysit stopped; unresolved blocker needs human and removed babysit:active Babysit agent is actively working this PR labels Nov 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

babysit:blocked Babysit stopped; unresolved blocker needs human

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant