Skip to content

Browser: hosted-session safety + Patchright stealth (supersedes #166, closes #168)#186

Merged
wu-changxing merged 8 commits into
mainfrom
browser-hosted-patchright
Jul 7, 2026
Merged

Browser: hosted-session safety + Patchright stealth (supersedes #166, closes #168)#186
wu-changxing merged 8 commits into
mainfrom
browser-hosted-patchright

Conversation

@yyy900

@yyy900 yyy900 commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

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:

  1. 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 any host() 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_PROXY egress, frame-aware selector clicks, the bounded screenshot payload, and the opt-in human_jitter / bind_browser_session plugins.

  2. Enable the isolation on the hosted agent + binding cleanups. bind_browser_session is what routes each session to its own tab, but nothing registered it — so on a co ai deploy 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-level threading.local onto the BrowserAutomation instance (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, and tab_idle_ttl / max_tabs become documented constructor kwargs instead of private attributes deployments poke.

  3. 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.enable CDP leaks and navigator.webdriver at the driver level — below where flags and init scripts operate, which is what Cloudflare / DataDome / Kasada actually fingerprint.

    • Swap the import (sync API is identical, so only the import line changes).
    • Drop the navigator.webdriver add_init_script shim — Patchright hides it at the driver level, and init-script injection is itself a timing-detectable tell.
    • Trim browser_config.py from 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.
    • Rename PLAYWRIGHT_AVAILABLEBROWSER_AVAILABLE (it no longer gates Playwright).
    • Update pyproject.toml, the browser template requirements, and the co-ai Dockerfile (patchright install --with-deps chrome).
  4. hosted-browser template (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 a host(create_agent) factory with bind_browser_session for per-session tabs; site workflows as .co/skills; credentials/2FA through ask_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 (keeps wait_for_manual_login for first login; the hosted path removes it because stdin never answers behind host()).

Verification

  • pytest tests/unit tests/e2e/cli/test_cli_init.py — 2019 passed, 5 skipped (environment: PIL/Windows/TUI extras).
  • Live end-to-end: BrowserAutomation launches real Chrome through Patchright, navigates to a live page, and navigator.webdriver reads false with the init script removed — confirming the driver-level patch works and the shim removal is safe.

Scope notes

  • The standalone raw-Playwright demos under examples/ are left as-is. They script Playwright directly (not BrowserAutomation), 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" and no_viewport=True (Patchright's other recommendations) are deferred: executable_path already pins real Chrome, and the screenshot-bounding logic depends on the deterministic 1920×1200 viewport. Worth revisiting as a deploy-time tuning pass.
  • Pre-existing: the template enumerations in docs/cli/{create,init,README}.md already don't list co-ai; they don't list hosted-browser either. Left for a docs pass.

Supersedes #166.

yyy900 added 4 commits July 7, 2026 11:05
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.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

yyy900 and others added 2 commits July 7, 2026 12:17
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>
@yyy900 yyy900 force-pushed the browser-hosted-patchright branch from 255c1c8 to e0db5e3 Compare July 7, 2026 02:17
yyy900 and others added 2 commits July 7, 2026 13:24
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 wu-changxing merged commit c00107a into main Jul 7, 2026
4 checks passed
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.

[FEATURE] Replace Playwright with Patchright for built-in anti-bot stealth

2 participants