Skip to content

fix(browser): survive a closed tab, pass Google's automation check and tear the browser down after one-shot runs - #1600

Merged
diillson merged 1 commit into
mainfrom
fix/browser-handoff-hardening
Sep 15, 2026
Merged

diillson merged 1 commit into
mainfrom
fix/browser-handoff-hardening

Conversation

@diillson

Copy link
Copy Markdown
Owner

Why

Field test of the showwait hand-off (PR #1598) against a real GitHub login, with the user signing in through Google, surfaced three defects.

What

  1. Google refused the sign-in ("This browser or app may not be secure"). Chrome under remote debugging reports navigator.webdriver === true unless --disable-blink-features=AutomationControlled is passed. The launcher now passes it (measured: truefalse), which is what that check looks at. Documented, with the fallback of attaching to the user's everyday Chrome via CHATCLI_BROWSER_CDP_URL.
  2. Closed tab/window killed the toolwait failed with a raw cdp error -32001: Session with given id not found. The session now enables target discovery, marks the page gone on Target.detachedFromTarget / Target.targetDestroyed for its own target, and recognises -32001 on page-scoped commands: they surface browser.ErrPageClosed; wait returns it as a result the model can act on; status tags it; the next command attaches a fresh blank tab before talking to the page (no relaunch, session and cookies kept). status never reattaches.
  3. Chrome outlived every chatcli -p run — the one-shot path returned without the interactive teardown, and logger.Fatal exits without unwinding. shutdownToolProcesses (browser + LSP pool + @proc supervisor) is now one helper shared by the interactive cleanup, deferred in HandleOneShotOrFatal, and called on each failure path before Fatal. Verified with the real binary: 0 Chrome processes left after a one-shot coder run that opened the browser.

Also: open about:blank was turned into https://about:blank (invalid navigation) — scheme-carrying URLs (about:, data:, file:, …) are now left alone.

Tests

  • pkg/browser: chromeArgs carries the flag and orders --headless=new first; fake browser that answers -32001 once the page is "closed" → ErrPageClosed, PageClosed(), exactly one new target created on the next call and the session drives it; detachedFromTarget marks the page closed, events for other targets are ignored, and the first call after a detach reattaches without ever seeing -32001.
  • cli/plugins: wait returns the closed-page message as a result and still propagates other errors; URL normalization table incl. about:blank reaching the backend untouched.
  • go test -race green on pkg/browser, cli/plugins; cli one-shot/config tests green; golangci-lint v2.13.2 0 issues.

…d tear the browser down after one-shot runs

Field test of the visible hand-off against a real GitHub login surfaced
three defects.

Google refused the sign-in with "this browser or app may not be secure":
Chrome under remote debugging reports navigator.webdriver as true unless
the AutomationControlled blink feature is disabled. The launcher now
passes that flag; a headed session reports webdriver false, which is
what the check looks at.

When the user closed the window afterwards, wait died with a raw CDP
"session with given id not found". The session now hears the detach and
destroy events for its page and recognises the error code: page-scoped
commands surface ErrPageClosed, wait reports the closed page as a result
the model can act on, status tags it, and the next command attaches a
fresh blank tab before talking to the page, so the session keeps working
without a relaunch.

The browser outlived every chatcli -p run: the one-shot path returned
without the interactive teardown and Fatal exited without unwinding. The
tool helper shutdown, browser, language servers and background processes,
is now one shared helper called from the interactive cleanup, deferred in
the one-shot handler and invoked on each of its failure paths before Fatal.

Also open no longer prefixes https:// onto about:, data:, file: and other
scheme-carrying URLs, which made about:blank an invalid navigation.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Gate

Result: ✅ all floors passed

Floor Status Result Δ vs main Budget
1 · Build & Static go build / vet / fmt / lint
2 · Coverage 59.7% (bootstrap) 0 ≥ baseline
3 · Patch coverage 72.8% (req ≥ 60%) ≥ 60%
4 · AI smells diff scanned
5 · Scope budget 8 files / 426 LOC (code 426 + tooling 0) warn 800·25
6 · E2E go test -race ./e2e/... ≤ 15min
7 · Commit lint conventional commits
8 · Cyclo (new code) 5 file(s) under threshold ≤ 30
9 · Secrets scan gitleaks
10 · i18n parity missing 0, unknown 0
11 · CRD drift drifted: 0
12 · License headers 0 missing
13 · API breaking 0 incompatible
14 · Binary size chatcli 96.1MB · operator 54.3MB 100MB each
15 · Provider parity 14 providers · 0 violations

Config: .github/quality-gate.yml. Workflow: .github/workflows/quality-gate.yml.

@diillson
diillson merged commit 92e5d08 into main Sep 15, 2026
25 checks passed
@diillson
diillson deleted the fix/browser-handoff-hardening branch September 15, 2026 20:09
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.

1 participant