BrowserRig Plan
August 28, 2026 ยท View on GitHub
BrowserRig is a local driver that lets trusted agents automate the user's already-running Chromium-family browser. It provides browser control, session isolation, and diagnostics; it does not call models or decide what to do.
Agent / MCP client / CLI
-> relay-backed execute session
-> local relay
-> browser extension
-> user's Chromium-family browser tabs
The end-to-end path is working. The current product bet is frictionless control of the user's real, signed-in browser: no separate automation profile, no browser-wide remote-debugging approval, and no toolbar click for the active tab. New features should not weaken the code-first interface or move behavior into the extension without a concrete browser-API reason.
Next Priorities
Work these in order unless field evidence changes the priority. Every item should land with unit or smoke evidence appropriate to the behavior.
1. Finish DSH ecosystem validation and launch
The native bundle, public npm package, clean-profile installation, and repository
discovery topics are shipped. Keep DSH discovery, stars, issues, releases, and
documentation pointed at this repository rather than creating a separate
dsh-browserrig product. The remaining launch work is runtime field evidence
and a useful, reproducible demonstration:
- Run one real signed-in-browser workflow through DSH, including active-tab adoption, inspect-act-verify execution, a screenshot attachment, and session continuation after restarting DSH.
- Publish that workflow as a reproducible demo in DSH discovery channels.
- Add the remaining DSH-specific negative coverage for host sandbox denial and stale package-local relay reporting. Malformed or truncated CLI output, child cancellation, and plugin unload after work starts already have direct tests.
- Verify no plugin call changes the saved human current session and unload never closes adopted tabs, deletes resumable sessions, or stops the detached relay.
- Repeat the packaged install and signed-in-browser smoke for every advertised DSH profile before each ecosystem-facing release.
- Treat ecosystem exposure as the result of task isolation, recovery, diagnostics, and signed-in-browser usefulness. Optimize for successful and retained use, not marketplace impressions alone.
2. Split the relay into testable responsibilities
Extract cohesive modules from makeRelay without changing the protocol:
- Deepen
CdpRouterwith command classification, guardrails, and compatibility shims. ExtensionEventHandler: extension event decoding and registry mutation.
CdpClientPool now owns client sockets, per-client attachment sets, aliases,
auto-attach settings, and connection generations. CdpRouter now owns
client-relative visibility, target inventory, target and alias resolution, and
exact root-versus-child Chrome session routing.
The goal is browser-free testing of routing and lifecycle behavior, not smaller
files for their own sake. Keep orchestration in makeRelay and avoid exposing
internal protocol details to the CLI or MCP server.
Verification:
- Extend reconnect, OOPIF, and multi-client smoke cases to cover root detach and conflicting client auto-attach settings.
3. Extend recording surfaces
- Add MCP recording start, stop, status, and cancel tools after the relay path is robust.
- Build the flight-recorder ring buffer only after chunk streaming lands.
Verification:
- Confirm CLI and MCP recording behavior match.
4. Resolve smaller agent-experience gaps
Verification:
- Add a local open-shadow-root form fixture for both fill helpers and assert the closed/no-match diagnostic separately.
- Add CLI parsing tests for positional,
--session, and-sreset/delete forms. - Assert reset/delete target the requested session and do not change the saved human-shell current session unexpectedly.
Recently Shipped
Handoffs return on a live destination context
After a human completes a navigation-triggering handoff, BrowserRig waits through transient execution-context replacement before returning to user code. The same execute can immediately inspect and verify the authenticated destination without repeating the human action.
Session cleanup is safely repeatable
Deleting a resolved session id is idempotent across HTTP, RelayClient, CLI, and an explicit MCP target. The structured result reports whether a live session was deleted, while CLI cleanup retries no longer turn an already-absent session into a failure. MCP keeps omitted-id deletion on one stable current target, but does not advertise the whole optional-target tool as retry-safe because an intervening bare execute can atomically recreate that current session.
Browser startup and delayed tab groups do not strand connectivity
The extension registers a global browser-start listener that repairs its alarm and requests a relay connection whenever Chromium starts the profile. Handshake readiness waits only for attached-debugger inventory; potentially suspended tab group cleanup runs afterward, reports failures through relay diagnostics, and serializes the latest ownership intent per tab without weakening active-tab generation binding.
The independent no-click BrowserRig release is public
No-click active-tab adoption is available through the CLI, MCP, and DSH bundle. Background tab creation preserves focus, automatic no-audio recording can fall back to CDP when an adopted tab has no Tab-Capture Grant, and the BrowserRig brand, first-run documentation, Store disclosures, deterministic packaging, and production origin all use the independent product identity.
The extension is publicly available from the
Chrome Web Store
under the committed Item ID dbobcmjamjdknplkplgdihdnmdjklpin.
BrowserRig is a native DeepSeek Harness bundle
The root npm package declares an official dsh.bundle patch and exports
browserrig/dsh; there is no separate repository or package. The plugin
registers code-first execute, active-tab adoption, scoped status, reset,
journal, and BrowserRig-owned issue-report tools plus concise prompt guidance,
so DSH users install neither a global BrowserRig CLI nor the standalone
BrowserRig skill. The browser extension remains an explicit user-authorized
installation.
The adapter invokes its package-local prebuilt CLI with fixed argv and validated bounded JSON envelopes instead of exposing shell passthrough. It forwards DSH cancellation and uses DSH attachment storage for image results when available. BrowserRig core remains independent of DSH.
Each DSH agent session receives an endpoint-scoped durable BrowserRig mapping.
Bare execute or bare active adoption creates and records the first session;
later calls continue it without exposing BrowserRig IDs to the model. First-use
operations serialize per DSH session, unrelated sessions stay isolated, and a
mapping is replaced only after a stable session-not-found response. Corrupt
mapping files fail closed and are never overwritten.
The public browserrig@0.3.0 npm tarball contains the bundle, declaration file,
package-local CLI, extension artifacts, and cordis.patch.yml with no install
lifecycle script. That exact public tarball composes successfully after isolated
installation into clean DSH web and headless profiles. The repository carries
the dsh-plugin and deepseek-harness topics alongside its existing browser and
agent discovery topics.
Tab-capture recordings stream with intrinsic framing
Extension protocol 3 retains protocol 2's sequenced BCRD binary recording
frames and adds no-click active-tab attachment. The relay validates framing and
sequence, bounds
pending writes, streams each tab to an adjacent temporary file, and atomically
renames complete recordings. Interleaving, oversized queues, malformed frames,
and output larger than a single frame have direct coverage.
Fill helpers traverse open shadow roots
String selectors passed to fillInput and fillInputs now search recursively
through open shadow roots. A zero-match error explains that closed roots remain
unavailable and suggests locator.fill() when Playwright can resolve the field.
Session lifecycle selectors are consistent
session reset and session delete accept positional ids, --session/-s,
and BROWSERRIG_SESSION before falling back to the saved current session.
Smoke coverage verifies explicit missing flag and environment ids fail instead
of falling back to the saved current session.
CDP routing fails closed
Identity-free Target.getTargetInfo no longer returns an arbitrary tab, and
otherwise-unhandled sessionless CDP commands require an explicit session. All
explicit target and session routing now rechecks client visibility, including
session-scoped auto-attach. Root teardown emits each announced child detach
before detaching the root so clients cannot retain orphaned sessions. The
browser-free CdpRouter module keeps these visibility, alias, and generation
rules out of relay transport orchestration. Browser-context permission and
cookie commands route through a session-owned root for named clients, including
multi-page sessions and browser CDP aliases, or exactly one visible root for raw
clients, without falling through from a named client to an unrelated tab.
CDP client state is isolated per connection
CdpClientPool now owns each CDP client's session identity, target
announcements, aliases, auto-attach settings, and idle-reset generation. New
targets use the originating client's auto-attach settings instead of global
last-writer-wins state. Ownership visibility changes also invalidate target
aliases, so a client cannot continue routing commands to a tab after it becomes
hidden.
Wedged session pages recover or fail fast
A 2026-07-09 field failure left a relay-owned page open but unusable after its
execution context was destroyed. Later calls each consumed their full timeout,
and the target remained at chrome-error://chromewebdata/.
BrowserRig now remembers context failures and browser crash events. Before the next normal execute, it gives the default page a one-second health check. An unhealthy relay-owned page is closed and recreated with a stale-reference warning; if it cannot be closed, execute fails with reset guidance instead of leaking ownership. An unhealthy adopted user tab is never closed or replaced; the execute fails quickly and tells the agent to reset or adopt another tab.
Inspector.targetCrashed and Target.targetCrashed events mark the target,
reject its pending debugger commands without disconnecting the extension, and
appear as crashed=true in status and doctor data. chrome-error:// targets are
also unhealthy. Cross-extension navigation failures receive the bounded
target/cross-extension-page diagnostic.
The execute-page-recovery smoke crashes a relay-owned renderer, asserts prompt
failure and status visibility, then verifies that the next execute receives a
fresh page. Unit coverage verifies that adopted pages are preserved.
Extension child targets stay subordinate
Unknown Target.targetInfoChanged events no longer overwrite a tab's root
target. URL-less child pages are held until their destination is known, and a
child that resolves to another extension is removed without changing the
session-owned page. This prevents password-manager UI from becoming the
session default or producing cross-extension navigation failures.
Downloads fail with an explicit capability boundary
Chromium rejects both Browser.setDownloadBehavior and the legacy
Page.setDownloadBehavior through a tab-scoped chrome.debugger attachment.
Without either command, stock Playwright cannot retain the GUID-named artifact
that backs download.saveAs(). BrowserRig therefore rejects
page.waitForEvent("download") immediately with the reason and a fetch-plus-fs
workaround rather than allowing a 30-second timeout. A local blob/fetch fixture
keeps this failure direct. Supporting native download artifacts later would
require a new extension capture protocol and permission model.
Product Boundaries
- Driver, not agent: BrowserRig never calls models or plans tasks.
- User browser first: the primary target is an already-running Chromium-family browser with the extension installed.
- Product-led upstream intake: advance one adjacent upstream minor series
at a time to that series' latest patch. Review upstream pull requests, adapt
selected behavior serially through independently reviewed batches, and keep
the durable cursor, authorization, progress, and archives in
docs/upstream-sync/. Never treat sync as product identity, release, or source-equivalence ownership. The first archived cycle completed selective intake through upstreamv0.5.1on 2026-08-28; no later range is currently active. - Trusted local execution:
execute(code)trusts the calling agent. It is not an untrusted-code security boundary. - Code-first control:
execute(code)is the primary interface. Dedicated tools exist only for lifecycle operations that benefit from explicit command semantics. - Playwright first: v1 uses stock
playwright-core. Custom behavior should not require a Playwright fork. - Local by default: the relay binds to trusted local interfaces. Remote access requires an explicit authentication design before it is added.
- Stable extension shim: Chrome API adaptation belongs in the extension; orchestration belongs in the relay so most changes require only a relay restart.
- Minimal extension UI: the toolbar controls attachment, while subtle in-page UI communicates attached, running, and waiting states. There is no side panel.
- Concise self-description:
browserrig skillprints one short, current workflow document. Do not split it into topic subcommands or require agents to perform a reading ceremony.
Distribution And Installation
- The independent product identity is BrowserRig across the repository, npm
package and CLI (
browserrig), MCP executable (browserrig-mcp), and Chrome Web Store listing. The public repository isCastor6/BrowserRig. - The
browserrigpackage is published publicly on npm. Normal setup installs that npm artifact; source development usespnpm install,pnpm build, andnpm link. - BrowserRig supports Node.js 22.22.0 and newer. CI, versioning, and release builds use Node.js 22.22.0 so the declared runtime floor and published artifacts stay aligned; bundled dependency engine ranges do not become separate user-facing version branches.
- The same prebuilt npm artifact is the DSH bundle. DSH installs it per profile
with
dsh plugin --profile <name> add browserrig; the bundle resolves its own matching CLI runtime and does not require a global command or separate skill. - Releasable pull requests carry Changesets. A scoped GitHub workflow maintains
one shared
Version Packagespull request that batches version and changelog updates. It authenticates with the encrypted, repository-scopedCHANGESETS_TOKENsecret so generated pull-request CI starts automatically. The privatebrowserrig-extensionworkspace package participates in the same Changesets plan without being published to npm. Extension changes declare a relative bump for both the private extension package andbrowserrig, because the built extension also ships in the npm tarball. CI rejects missing release intent, a smaller npm bump, and direct version edits, while the version workflow calculates the exact versions and synchronizes the extension result intoextension/manifest.json. Store-listing-only artwork changes neither plan. A maintainer decides when to merge it. Merging that repository-owned branch automatically builds one immutable npm and extension release candidate at the exact merge commit, records its component versions and checksums, and publishes the exact npm tarball through a direct-publish OIDC trusted publisher. After npm succeeds, the same workflow downloads and verifies that retained candidate, exchanges GitHub OIDC for a short-lived Google service account token, and submits its exact extension ZIP through Chrome Web Store API V2 withDEFAULT_PUBLISH. Store review remains mandatory; approval makes the update public automatically. Merging the version pull request is the explicit and irreversible release approval for both channels. A manual rebuild path remains available but never publishes either artifact. After the exact tarball becomes public, a separate scheduled finalizer verifies its registry integrity against the retained candidate, creates the npm-version Git tag and GitHub Release, and attaches the original npm tarball, extension ZIP, manifest, and checksums. It never rebuilds or overwrites a release. - The browser extension is publicly available from the
Chrome Web Store.
Store installations receive approved updates automatically. The
0.0.1bootstrap package created the independent listing; exact later Store versions are derived from extension Changesets. The npm package'sextension/distremains available for source development and Store-inaccessible browsers. - The production-origin allowlist and committed public manifest key pin Store
Item ID
dbobcmjamjdknplkplgdihdnmdjklpin. Test a production relay against a same-ID unpacked build before Store submission; never broaden this to an arbitrary extension origin. - Extension and npm releases are independently versioned. The extension hello reports an explicit protocol version, and compatibility rather than exact package-version equality determines whether the local driver may use it. GitHub tags and Releases follow the npm version and record the extension and protocol versions as component metadata.
- Browser data crosses only the loopback connection unless an authorized local
caller sends returned data elsewhere. The default endpoint is
127.0.0.1:19990. - Extension source and packaged-asset changes require a
browserrig-extensionChangeset, rebuilding, and reloading the unpacked extension. Relay-only changes do not. pnpm package:extensionproduces the deterministicbrowserrig-extension-<version>.zipChrome Web Store review artifact. The release workflow uploads those exact retained bytes and refuses conflicting, staged, warned, or taken-down Store state. A bundled unpacked extension belongs to development and future managed-browser launch flows.
Session And Tab Model
An attached tab is a browser target exposed by the extension. An unowned
attached tab remains visible to connected clients for explicit recovery and raw
CDP workflows. A BrowserRig session owns one default page and persistent
JavaScript state; normal execute calls use that page instead of choosing an
arbitrary tab from the attached pool.
- Bare CLI execute atomically creates a fresh readable session and prints its id.
--sessionorBROWSERRIG_SESSIONexplicitly continues a CLI session.- One MCP server process owns one implicit execute session. Explicit MCP session management remains available for lifecycle operations.
- One DSH agent session owns one plugin-managed BrowserRig session mapping per relay endpoint. BrowserRig ids remain internal to the adapter.
- The CLI never infers an agent's session from human-shell current state.
- Human session-management commands store their endpoint-scoped current id in
~/.browserrig/session.json. - The relay stores private session descriptors under
~/.browserrig/relays/<port>/sessions.json. Relay restart restores ids, read-only mode, and exact target ownership when the tab reappears; JavaScriptstateand snapshot refs reset with an explicit warning. - A session owns one default page. Relay-created pages persist across short-lived CLI connections.
session adoptmakes an attached user tab the session's default page and closes the session's previous relay-created page.- Adoption is exclusive: one target can belong to only one BrowserRig
session.
TargetRegistryis the ownership authority; session state retains only the adopted default-page pointer. - Adoption reserves target ownership before Playwright resolves the page, then commits or rolls back as one serialized transaction. A caller timeout rolls back visibility immediately while the worker retains the execute and adopt permits until any uncancellable Playwright work settles.
- Reset, delete, or detach releases an adopted tab without closing it.
- Reset and delete acquire the execute permit before closing a sandbox, so they cannot interrupt a running script.
- Reset and delete give an absent persisted relay target a bounded opportunity to re-announce. A completed protocol-v1 inventory, or expiry of the reconnect grace, declares that identity dead so recovery cannot require catalog edits. The relay never guesses a physical tab to close when the live target identity is unavailable.
- Corrupt session catalogs fail relay startup without being overwritten.
- The relay wins the endpoint port before loading the catalog or enabling catalog writes. Lifecycle responses wait for atomic file replacement, file sync, and directory sync before acknowledging durable state.
- Session-owned tabs share a purple
BrowserRiggroup within each browser window. Merely attached, unowned tabs stay in their existing location. - Explicit URL selection must match exactly one page. URL and index selectors cannot be combined.
CDP target visibility is scoped per client. Session-owned tabs and their events are visible only to that session's clients; unowned tabs are visible to all clients. This prevents concurrent Playwright clients from double-initializing a page while retaining explicit attached-tab recovery. Every ownership change reconciles existing client announcements, browser grouping, and page status.
Current Capabilities
Execute
- Navigate, inspect, click, fill, wait, evaluate, and capture screenshots with stock Playwright APIs.
- Create tabs through
context.newPage()and preserve sessionstateacross execute calls. - Run inline code or
--file <path>scripts, with conservative auto-return for single expressions such aspage.url(). - Expose selected Node built-ins:
fs,path,os,crypto,url,util,events,stream,buffer,http,https, andzlib.child_processis not exposed by default. - Return structured values, script and page logs, page errors, warnings, diagnostics, session identity, and per-call aftermath.
- Health-check a default page after execution-context failure or a crash event. Recreate unhealthy relay-owned pages and preserve unhealthy adopted tabs.
- Transfer returned PNG, JPEG, and WebP buffers through a dedicated media channel. MCP emits native image attachments without temporary files or duplicated base64 metadata.
Authenticated Network Capture
- Each Execute Sandbox owns one normalized network recorder that follows its default or adopted page across execute calls and page recovery.
- Playwright page events capture root-frame and child-frame exchanges. HAR is an export adapter, not the recorder's domain model.
- Request and response bodies have per-body and aggregate byte budgets; truncation, failures, and dropped-entry counts remain visible in summaries.
- Written artifacts always replace credential-bearing headers, cookies, query
parameters, and structured body fields with stable
BROWSERRIG_SECRET_Nreferences. - Named secret profiles retain lossless values in restrictive local files. Cross-process locks serialize profile publication; repeated captures and reload-based refresh preserve references by observed request source.
secrets runinjects profile values into a child process and redacts known values from bounded stdout and stderr before returning them.- While capture is active, values observed in completed exchanges and secret-shaped returned data are removed from execute results, URLs, logs, and journal records before they leave the sandbox.
- CLI, MCP, and execute-sandbox helpers call the same session-owned recorder. Capture is cancelled on session reset, deletion, and relay shutdown.
Inspection And Interaction Helpers
snapshot(options?)provides a bounded semantic read-before-act view.snapshot({ diff: true })compares against the previous compatible snapshot and exposes refs only for current additions or changes.ref(id)resolves controls from the latest valid snapshot and fails closed after navigation or incompatible DOM drift.ariaSnapshot()and raw Playwright provide deeper inspection when compact snapshots are insufficient. The helper omits native text-control values, custom ARIA range values, and editable content across SVG and open-shadow boundaries. Each isolated-world mask is scoped to its activation frame and a module-unique token, so concurrent guarded snapshots restore safely without depending on unrelated frames. It must settle before unrelated same-page operations run while the mask is active.screenshotWithLabels({ page, path? })annotates likely interactive elements and returns label metadata.fillInputandfillInputsprovide a DOM-evaluation fallback when browser extensions make native Playwright filling hang.- Allowed Playwright mouse actions can reveal a spring-animated cursor.
showGhostCursor(),hideGhostCursor(), andghostCursor.show/hideprovide explicit cosmetic control.
Human Control And Safety
- The extension toolbar attaches or detaches the active tab. A toolbar click cannot detach a tab while its session is executing or waiting for a handoff.
handoff(message, { timeoutMs, start? })binds a waiter to the exact page target, survives top-level navigation, and resumes only from the matching in-page completion control. The relay ignores ambiguoustarget_closedevents from extension child targets, so the extension preserves the WAIT UI until the relay confirms a root detach or the tab is removed.startregisters WAIT state before invoking a prompt-triggering action, so native WebAuthn or payment UI cannot block the script before handoff exists. It runs only after the extension acknowledges WAIT. Human completion waits for the action to settle and the destination execution context to become available; timeout or target cancellation disconnects the sandbox's Playwright connection before releasing the execute permit, so a non-settling action cannot mutate the page later.- Destructive browser-state CDP methods such as
Browser.closeand cookie or cache clearing are always blocked. - Read-only sessions additionally reject
Input.*. They reduce trusted mistakes; they do not prevent mutation throughpage.evaluate. - Safe destructive UI scripts inspect the target first, validate confirmation dialog text inside the approved action, and verify the result through an independent read path.
Operations And Diagnostics
- Relay-backed CLI and MCP commands share one detached relay and start it when
needed. The relay outlives the MCP process, so a CLI handoff is not coupled to
MCP lifecycle.
statusanddoctorremain observational;serveis the foreground debugging path. doctorreports relay and extension versions, build mismatches, sessions, active targets, child targets, crashed/browser-error targets, and built artifacts.browserrig skillprints the concise, current agent workflow.- Each execute appends a best-effort bounded entry to
~/.browserrig/sessions/<id>/journal.jsonl. - CLI
browserrig issue report, MCPissue_report, and DSHbrowserrig_issue_reportshare a relay-independent issue sink under~/.browserrig/issues/. It sanitizes structured fields, references journal timestamps without copying execute material, and aggregates matching fingerprints. Operational records remain local; security records are never public; suspected bugs may be submitted toCastor6/BrowserRigthrough an installed authenticatedghonly after the user setsBROWSERRIG_ISSUE_AUTO_SUBMIT=true. - Recording supports extension
chrome.tabCaptureWebM for user-owned tabs and relay-owned CDP screencasting to WebM or MP4.
Architecture Decisions
The relay owns orchestration
- Node-side code uses Effect v4, with
a local
effect-smolcheckout as the API and pattern reference. - Effect-returning functions prefer
Effect.fnorEffect.fnUntraced. - Playwright and relay resources use scoped lifecycles.
- Application configuration uses Effect
Config; direct environment access is limited to synchronous Node process adapters. - The extension remains plain TypeScript and browser-native.
- The extension protocol remains custom JSON over websocket until schema or versioning needs justify Effect RPC. Its shared pure validators reject malformed commands and envelopes without pulling Effect into the MV3 shim.
- Authenticated capture stays in the relay-backed session sandbox. The extension forwards the CDP traffic Playwright already needs; Node handles correlation, budgets, export, credential profiles, and refresh.
One schema and one client define the relay boundary
- HTTP wire shapes live in
src/relay-schema.tsas Effect Schemas. - Responders and clients derive types from those schemas rather than hand-written JSON checks.
- CLI and MCP relay access goes through
src/relay-client.ts; neither maintains an ad hoc HTTP client. - The public Effect client uses the same typed relay client. It atomically ensures named sessions and exposes an origin-bound capability for structured JSON requests in the live default page.
- Authenticated-origin requests use page-context
window.fetch, never exported cookies or Secret Profiles. They pin an exact origin, accept relative paths, block redirects, bound response bytes, and never retry mutations. - Sensitive authenticated responses bypass execute journals, return as Effect
Redactedvalues, setCache-Control: no-store, and fail closed while a session Network Capture is active. - The public client reveals sensitive responses through its own
revealoperation so package-manager layouts with multiple Effect instances do not cross incompatible module-local Redacted registries. - Boundary failures use tagged schema errors and a shared coded error envelope. The relay retains its message as the top-level human-readable message while clients can branch on stable codes for invalid requests, missing resources, ownership conflicts, lifecycle conflicts, and internal failures.
- Each HTTP effect is interrupted when its response closes. Execute protects the underlying uncancellable Playwright Promise so its session permit remains held until browser work actually settles.
- Corrupt current-session persistence fails visibly and remains untouched rather than being interpreted as an empty store.
CDP relay invariants preserve reconnect correctness
- Store a root page target before applying
Target.setAutoAttach; Chrome can emit child or OOPIF attachment events immediately. - Forward routable dedicated workers to Playwright. Resume and suppress paused unsupported children, such as page-scoped service workers, so they cannot block parent navigation.
- Replay stored child attachments and current child-frame navigation when an OOPIF reconnects.
- Never announce one target id twice to the same client. Emit
Target.detachedFromTargetbefore re-announcing it with a new session id. - Treat a new root target/session generation for an existing physical tab as a replacement transaction: preserve committed ownership, roll back provisional adoption ownership, detach old clients and children, rebind handoffs, and reacquire the new Playwright page by exact target id.
- Await HTTP and websocket close callbacks during relay shutdown so tests and smoke runs do not leak ports or listeners.
- Relay shutdown closes the adoption gate and drains active or queued adoption workers before session resources. It never interrupts a worker whose underlying Playwright Promise may still mutate its sandbox.
A command timeout does not imply a dead extension
A timed-out extension RPC fails only that command. The relay closes the extension socket only when a websocket ping also fails. This prevents one dialog-blocked tab from destroying every session's relay state.
Execute output describes one call
Warnings and aftermath belong to the execute call that caused them. Aftermath
tracks URL movement, main-frame navigation, console and page errors, and
handoffs. The relay does not install a passive page.on("dialog") listener
because that would suppress Playwright's auto-dismiss behavior and can hang the
page.
Debug traces exclude user data
With BROWSERRIG_DEBUG=1, [browserrig:ctx] logs contain bounded target,
ownership, context-lifecycle, loader, reset, and error-shape metadata. They do
not contain expressions, arguments, results, headers, cookies, or form values.
Builds provide runtime identity
scripts/build-cli.ts injects the package version and build id. Source runs use
0.0.0-dev and a deterministic fingerprint of src/*.ts, package.json, and
pnpm-lock.yaml; runtime code does not hardcode release versions. The
relay reports both values so doctor can identify a stale long-running relay.
It also reports an instance id, start time, and PID; bounded managed-relay
process-fault diagnostics are retained locally so unexpected same-build
restarts can be distinguished from session eviction.
Operational commands replace an older managed relay only after confirming its
exact instance id, then wait for that process to exit before starting the
current build. Build ordering preserves BrowserRig's deterministic content
hash: a higher stable package version is newer, while same-version replacement
also requires a newer artifact at the same resolved managed CLI path. Source,
foreground, differently installed same-version, and newer relays fail closed
with restart guidance.
Public clients tolerate extension reconnect windows
BrowserRigClient.make gives a matching pre-existing relay the same bounded
extension reconnect grace used after relay startup. Session summaries report
connected only when the Playwright transport and a live default page are both
available, and remain disconnected while a known-crashed page awaits recovery.
Known Limitations
- BrowserRig does not expose custom
page.sessionId(),page.targetId(),frame.frameId(), orlocator.selector()APIs. - Raw CDP behavior has no guarantees beyond stock Playwright and the relay's documented guardrails.
- Native
locator.fill()can hang on login-style fields when installed browser extensions inject focus handlers or overlays.fillInputis the explicit fallback for ordinaryinputandtextareaelements. fillInputcannot reach fields inside closed shadow roots.- OOPIF behavior is guaranteed only by the current reconnect smoke scenarios.
- Clipboard automation on insecure origins is not guaranteed.
- Playwright download events and
download.saveAs()are unavailable in extension-backed tabs because Chromium blocks download behavior commands fromchrome.debugger; download waits return a direct capability error. - CDP recording activates its tab to avoid background compositor throttling,
fits the viewport within 1280x720, requires
ffmpegonPATH, and does not capture audio. - A no-click Active-Tab Attach does not create Chrome's Tab-Capture Grant. Automatic, no-audio recording falls back to CDP when that grant is absent; explicit tab-capture and audio recording require one toolbar invocation.
- The trusted sandbox exposes selected Node built-ins, not unrestricted local command execution.
- Exact parity across third-party authentication remains a manual diagnostic. Compare the same starting URL and browser profile in a fresh relay-owned tab and an authenticated adopted tab without automating credentials, tokens, or production account state.
Backlog
These items are accepted directions but are not current priorities:
- Keep approximately the last 60 seconds of CDP frames in a flight-recorder ring
buffer and support
recording save-last 30safter recording streaming is bounded. - Harden extension reconnect handling with
addEventListener, one source for thehellomessage, and a bounded outbound event queue if lost debugger events continue to matter in practice. - Add optional managed browser launch, including Brave and profile selection.
- Scope the saved human-shell current session by browser profile, in addition to relay endpoint, if multiple profiles become a supported workflow.
- Add stricter workspace or session ownership only if the loose shared attached tab pool causes concrete failures.
- Bundle an unpacked extension for managed and development browser launch.
- Add token-authenticated remote relay mode only if the relay must bind beyond trusted local interfaces.
- Evaluate custom CDP or a smaller Playwright-compatible client only after stock Playwright creates a concrete blocker.
- Add richer compact snapshot semantics only in response to demonstrated agent failures.
- Add broader local execution behind an explicit capability only when an agent workflow requires it.
- Consider a right-click
send element to BrowserRigpin if handoff evidence shows a recurring element-selection problem. - Add true mid-script cancellation. Toolbar clicks currently preserve active executes and handoffs rather than interrupting them.
Explicitly declined for now:
- Dedicated observation commands such as
browserrig snapshot,screenshot, orlogs; execute-level helpers are sufficient. - Topic-specific
skillsubcommands; the workflow should remain one concise document. - A side panel.
Historical Milestone
The first milestone proved the complete path from toolbar attachment through the
extension and relay to stock Playwright. It also proved navigation,
context.newPage(), reconnect, OOPIF replay, dedicated workers, concurrent
session isolation, compact snapshots, handoffs, media returns, and local fixture
checkout flows.
The current smoke matrix covers local forms, cart and checkout, reconnect and
redirect reconnect, explicit target selection, crashed- and detached-page
recovery, fill helpers with string and Locator targets, snapshot refs, handoff
navigation and cross-tab binding, OOPIF reconnect, dedicated workers, the
download capability boundary, cursor behavior, session isolation,
multi-client visibility, stale-client ordering, and raw-client checkout.
Historical milestone scope is no longer used as the active backlog; Next Priorities is authoritative.