Browser: hosted-session safety + Patchright stealth (supersedes #166, closes #168)#186
Merged
Conversation
Serialize BrowserAutomation's public calls through one Playwright-owning worker thread so a shared instance is safe to call from any host() thread, and give each session (chat panel) its own tab in the shared login context so concurrent sessions stop navigating over one another. Reclaim idle tabs by TTL and an LRU backstop, restoring a reclaimed session's last URL on next use. Adds portable login state (save_state/seed_state), BROWSER_PROXY egress, frame-aware selector clicks, a bounded screenshot payload, and the opt-in human_jitter and bind_browser_session plugins. Ported from #166 onto current main.
The per-session tab machinery only activates when bind_browser_session runs before each tool, but no agent registered it — so on a co ai deploy every chat panel still shared one tab, the exact bug the machinery fixes. Register the plugin on create_coding_agent (the hosted browser agent) and assert it stays wired.
Patchright is a stealth-patched, API-compatible drop-in for Playwright. It removes the Runtime.enable / Console.enable CDP leaks and the navigator.webdriver flag at the driver level — below where Chrome flags and page init scripts operate, which is exactly what modern bot-detection (Cloudflare, DataDome, Kasada) reads. - swap the import; the sync API is identical so only the import line changes - drop the navigator.webdriver add_init_script shim (Patchright hides it, and init-script injection is itself a timing-detectable tell) - trim browser_config.py from the 53-flag browser-use mirror to only run-environment flags; Patchright owns anti-detection now, and its docs warn that over-configuring launch args can defeat the patches. Removes the standing browser-use sync burden. - rename PLAYWRIGHT_AVAILABLE -> BROWSER_AVAILABLE (it no longer gates Playwright) - update pyproject, the browser template requirements, and the co-ai Dockerfile (patchright install --with-deps chrome) Verified live: BrowserAutomation launches real Chrome via patchright, navigates, and navigator.webdriver reads false with no init script. Standalone raw-Playwright demos under examples/ are left as-is (not the SDK browser path).
The built-in browser tools ship Patchright now; the doc's pip/install commands pointed users at Playwright.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Cleanups on the per-session tab machinery, mechanism unchanged (the opt-in bind_browser_session plugin stays the way a hosted agent enables isolation on a shared browser): - The session binding moves off a module-level threading.local onto the BrowserAutomation instance (per instance, per thread). No shared module state between browser instances, and the test suites drop their autouse reset fixtures — a fresh instance starts unbound. - The plugin's handler is renamed _bind_browser_session so it no longer shadows the browser method (_bind_session) it calls; docstrings spell out the handler-vs-method split and who registers the plugin. - tab_idle_ttl / max_tabs become documented constructor kwargs so memory-tight deployments stop poking private attributes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…agent The shape proven by the live LinkedIn agent, packaged as 'co init/create --template hosted-browser': one shared Patchright browser (persistent login profile, headed under Xvfb) served to many sessions via a host(create_agent) factory with bind_browser_session giving each session its own tab; site workflows as .co/skills; credentials and 2FA through ask_user; CAPTCHA = report-and-stop; and the memory bounds that keep a small deploy box alive (tab TTL/cap kwargs, a browser idle reaper class). Local one-shot mode (python agent.py "task") keeps wait_for_manual_login for first login; the hosted path removes it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
255c1c8 to
e0db5e3
Compare
The name set was stringly-typed and far from the methods it exempted: rename open_browser/close/save_state and the set silently stops matching, so the method starts auto-creating tabs with no error. The marker sits on the method itself and survives renames. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two kinds of stragglers left behind by earlier renames: - The interactive create menu still offered a 'playwright' template that has no templates/playwright/ directory (renamed to 'browser' long ago), so picking it always died with "Template not found". The menu now lists the real templates (browser, hosted-browser) and the --template help text / module notes on init/create stop advertising 'playwright'. - User-facing install instructions for the SDK browser path still said 'pip install playwright && playwright install chromium' while the code and its error message moved to patchright + chrome. Docs now match. Raw-Playwright tutorial examples (custom stateful tools in docs/concepts, best-practices) and the sync_playwright/PLAYWRIGHT_BROWSERS_PATH API names stay: patchright exports Playwright's names on purpose. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
wu-changxing
approved these changes
Jul 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rebuilds #166 on top of current
main(that branch had drifted 40 commits behind and no longer merged cleanly), wires its main feature up on the hosted agent, and folds in the Patchright swap from #168.Four things, one per commit group:
Per-session tabs + hosted-session safety (ported from Make browser tools safe for hosted sessions #166). Serialize
BrowserAutomation's public calls onto one Playwright-owning worker thread so a shared instance is safe to call from anyhost()thread, and give each chat session its own tab in the shared login context so concurrent panels stop navigating over one another. Idle tabs are reclaimed by TTL and an LRU backstop, restoring a reclaimed session's last URL on next use. Also brings the portable login state (save_state/seed_state),BROWSER_PROXYegress, frame-aware selector clicks, the bounded screenshot payload, and the opt-inhuman_jitter/bind_browser_sessionplugins.Enable the isolation on the hosted agent + binding cleanups.
bind_browser_sessionis what routes each session to its own tab, but nothing registered it — so on aco aideploy every chat panel still shared one tab, the exact bug the machinery fixes.create_coding_agent(co ai's agent definition, which already encodes hosted-runtime decisions like removing the stdin-blocking login helper) now registers the plugin, with a test that keeps it wired. Session routing stays an opt-in plugin — it only matters when one browser is shared across hosted sessions, and the agents that do that enable it (co ai here, linkedin-agent-3 in prod, the new template below). Cleanups on the machinery: the binding moves off a module-levelthreading.localonto theBrowserAutomationinstance (no shared module state; test suites drop their reset fixtures), the plugin handler is renamed so it no longer shadows the browser method it calls, andtab_idle_ttl/max_tabsbecome documented constructor kwargs instead of private attributes deployments poke.Replace Playwright with Patchright (closes [FEATURE] Replace Playwright with Patchright for built-in anti-bot stealth #168). Patchright is a stealth-patched, API-compatible drop-in that removes the
Runtime.enable/Console.enableCDP leaks andnavigator.webdriverat the driver level — below where flags and init scripts operate, which is what Cloudflare / DataDome / Kasada actually fingerprint.navigator.webdriveradd_init_scriptshim — Patchright hides it at the driver level, and init-script injection is itself a timing-detectable tell.browser_config.pyfrom the 53-flag browser-use mirror down to run-environment flags only. Patchright owns anti-detection now and its docs warn that over-configuring launch args can defeat the patches; this also removes the standing "keep in sync with browser-use" burden.PLAYWRIGHT_AVAILABLE→BROWSER_AVAILABLE(it no longer gates Playwright).pyproject.toml, the browser template requirements, and the co-aiDockerfile(patchright install --with-deps chrome).hosted-browsertemplate (co init/create --template hosted-browser). The production shape proven by the live LinkedIn agent, packaged: one shared stealth browser (persistent login profile, headed under Xvfb) served to many sessions via ahost(create_agent)factory withbind_browser_sessionfor per-session tabs; site workflows as.co/skills; credentials/2FA throughask_user; CAPTCHA = report-and-stop; memory bounds for small deploy boxes (tab TTL/cap kwargs + a browser idle reaper).python agent.py "task"gives a local one-shot mode for developing skills (keepswait_for_manual_loginfor first login; the hosted path removes it because stdin never answers behindhost()).Verification
pytest tests/unit tests/e2e/cli/test_cli_init.py— 2019 passed, 5 skipped (environment: PIL/Windows/TUI extras).BrowserAutomationlaunches real Chrome through Patchright, navigates to a live page, andnavigator.webdriverreadsfalsewith the init script removed — confirming the driver-level patch works and the shim removal is safe.Scope notes
examples/are left as-is. They script Playwright directly (notBrowserAutomation), aren't packaged or tested, and are named/titled "playwright"; a half-swap would be incoherent and a rename is out of scope. The SDK path that actually does anti-bot is fully migrated.channel="chrome"andno_viewport=True(Patchright's other recommendations) are deferred:executable_pathalready pins real Chrome, and the screenshot-bounding logic depends on the deterministic 1920×1200 viewport. Worth revisiting as a deploy-time tuning pass.docs/cli/{create,init,README}.mdalready don't listco-ai; they don't listhosted-browsereither. Left for a docs pass.Supersedes #166.