Skip to content

feat(browser): restore driver "extension" via a loopback Chrome extension relay (no remote-debugging prompt)#100619

Merged
steipete merged 6 commits into
mainfrom
claude/nostalgic-mcclintock-6d47c4
Jul 6, 2026
Merged

feat(browser): restore driver "extension" via a loopback Chrome extension relay (no remote-debugging prompt)#100619
steipete merged 6 commits into
mainfrom
claude/nostalgic-mcclintock-6d47c4

Conversation

@steipete

@steipete steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Driving your signed-in Chrome with OpenClaw today means the user profile
(driver: "existing-session"), which attaches over Chrome's remote-debugging
port. Chrome then shows a blocking modal — "Allow remote debugging?" — that
must be clicked on the desktop. When you drive OpenClaw from a phone
(Telegram/WhatsApp) with nobody at the computer, automation stalls on a dialog
nobody can dismiss.

The old driver: "extension" relay that handled this was removed in 2026.3.22
(#47893), with no replacement for signed-in browsers away from the keyboard
(regression tracked in #53599).

Why This Change Was Made

I compared our integration with how Codex and Claude solve the same thing
(Codex source inspected directly in the sibling openai/codex checkout; Claude
via public docs + teardowns):

OpenClaw today Codex / Claude-in-Chrome
Signed-in browser attach remote-debugging port (CDP) chrome.debugger via a Web Store extension
Consent prompt blocking "Allow remote debugging?" modal dismissible "started debugging" infobar only
Away-from-desk broken (nobody to click) works
Automated-tab UX none colored tab group

Both Codex and Claude use the same shape: an MV3 extension that drives tabs with
chrome.debugger (no remote-debugging port → no modal) and scopes automated
tabs into a colored tab group. This PR brings that shape to OpenClaw and
restores driver: "extension".

User Impact

New built-in chrome browser profile (driver: "extension"):

  • No "Allow remote debugging?" prompt — drives Chrome via chrome.debugger,
    so it works when you are away from the computer.
  • Tab-group consent boundary — the agent only sees/controls tabs in the
    "OpenClaw" tab group. Drag a tab in to share, out to revoke; the agent's own
    tabs land there automatically. Same model as Codex/Claude.
  • Setup: openclaw browser extension path (load unpacked) →
    openclaw browser extension pair (paste the pairing string into the popup).
  • Security: the relay binds loopback only; both WebSocket sides authenticate
    with a token derived (HMAC-SHA256) from gateway auth, so the raw gateway
    credential never reaches Chrome; the extension upgrade is origin- and
    loopback-Host-checked. Compared with the user profile (whole signed-in
    browser exposed once you approve the prompt), the shared surface stays scoped
    to a tab group you control at a glance.

Existing openclaw, user, and remote-CDP profiles are unchanged. Doctor keeps
migrating legacy config; the old relay-era cdpUrl on extension profiles is
stripped (the relay owns its endpoint now).

Scope note: this restores the same-host case (gateway + Chrome on one
machine, driven remotely via a channel) with the modern tab-group UX. The relay
is loopback-only; exposing it for the fully cross-machine topology in #53599
(gateway on a VPS, browser on a laptop) is a follow-up — it should ride the
existing gateway node-host/tunnel path rather than opening a new network port.

Architecture

  • extensions/browser/src/browser/extension-relay/ — the relay is TypeScript and
    testable. relay-bridge.ts owns all CDP target synthesis (emulates the
    browser target for Playwright's connectOverCDP, maps session-scoped commands
    and child/iframe sessions to chrome.debugger). The removed extension put this
    logic in a 1000-line untestable MV3 service worker — that is why it rotted.
  • extensions/browser/chrome-extension/ — a thin MV3 extension: WebSocket client
    • chrome.debugger forwarding + tab-group management, nothing more. Pure logic
      (pairing parse, backoff, color mapping) is split into a unit-tested module.
  • Built-in chrome profile; distinct relay ports per extension profile; relay
    reconciles on auth rotation / cdpPort change and prunes removed profiles.

Evidence

Validated on Blacksmith Testbox (Linux, Node 24):

  • pnpm test extensions/browser1432 passed (14 new relay/extension tests:
    bridge CDP synthesis, session routing, child-session reap, auth derivation,
    pairing round-trip, port assignment).
  • pnpm tsgo:core, pnpm tsgo:extensions, pnpm tsgo:core:test — all green.
  • oxfmt --check + oxlint on changed files — clean.
  • Two adversarial review passes over the relay bridge and lifecycle wiring; all
    confirmed findings fixed (child-session map leak, attach-in-flight race on tab
    regroup, extension attach idempotency, multi-profile port collision, relay
    reconciliation on auth rotation) with regression tests.

Not yet exercised: a live end-to-end load of the unpacked extension against a
real Chrome (no headed Chrome in CI). The relay/bridge contract is covered by
the Playwright-handshake unit tests; a manual smoke on a real desktop is the
recommended pre-merge check.

Refs #53599

@steipete
steipete requested a review from a team as a code owner July 6, 2026 05:31
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation commands Command implementations agents Agent runtime and tooling size: XL maintainer Maintainer-authored PR labels Jul 6, 2026
Comment thread extensions/browser/src/browser/extension-relay/relay-auth.ts Fixed
Comment thread extensions/browser/src/browser/extension-relay/relay-server.ts Fixed

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

Copy link
Copy Markdown
Contributor

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: 5348cfe902

ℹ️ 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".

Browser: identity?.browserVersion ?? "Chrome/unknown",
"Protocol-Version": "1.3",
"User-Agent": identity?.userAgent ?? "unknown",
webSocketDebuggerUrl: `ws://127.0.0.1:${resolvedPort()}/cdp`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Return an authenticated CDP WebSocket URL

For extension profiles with gateway auth, /json/version is fetched through profile.cdpUrl with Basic auth, but this returned webSocketDebuggerUrl drops the token. The same relay rejects /cdp upgrades unless isAuthorized passes, and the existing CDP helpers/Playwright path use the returned WS URL for the next connection, so a paired extension can make HTTP discovery succeed while every CDP handshake is rejected as unauthorized.

Useful? React with 👍 / 👎.

const resolved = resolveBrowserConfig(cfg.browser, cfg);
for (const [name, profile] of Object.entries(resolved.profiles)) {
if (profile.driver === "extension") {
return { name, relayPort: profile.cdpPort ?? resolved.extensionRelayDefaultPort };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Use per-profile relay ports when pairing

When a user configures another driver: "extension" profile alongside the built-in chrome profile, config resolution assigns distinct ports via resolved.extensionRelayPorts, but the pairing CLI ignores that map and falls back to extensionRelayDefaultPort for any unpinned profile. In a config like profiles.work.driver = "extension", work is the first extension profile but may actually be assigned defaultPort - 1, so openclaw browser extension pair prints the wrong relay endpoint and pairs the extension to a different profile or a non-matching port.

Useful? React with 👍 / 👎.

@clawsweeper

clawsweeper Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 6, 2026, 3:51 AM ET / 07:51 UTC.

Summary
The PR adds a bundled Chrome MV3 extension, extension relay server/bridge, driver: "extension" browser profile support, pairing CLI commands, doctor/config migration changes, tests, and docs for signed-in Chrome control without the remote-debugging prompt.

PR surface: Source +2439, Tests +517, Docs +139, Config +3. Total +3098 across 47 files.

Reproducibility: yes. at source level for the review blockers: the relay returns an unauthenticated discovered WebSocket URL, pairing ignores resolved extension-profile ports, and synthetic page targets omit Playwright's required browserContextId. A live Chrome extension reproduction was not provided.

Review metrics: 2 noteworthy metrics.

  • Browser config/default surfaces: 1 driver value added, 1 built-in profile added, 1 doctor migration changed. These are compatibility-sensitive surfaces for existing config files, upgrades, setup docs, and profile selection.
  • Chrome extension permission surface: 5 permissions requested. debugger, tabs, tabGroups, storage, and alarms govern signed-in browser control and need explicit security review before merge.

Stored data model
Persistent data-model change detected: migration/backfill/repair: extensions/browser/src/browser/doctor.ts, migration/backfill/repair: src/commands/doctor/shared/legacy-config-core-normalizers.ts, serialized state: extensions/browser/src/browser/extension-relay/relay-bridge.test.ts, serialized state: extensions/browser/src/browser/extension-relay/relay-protocol.test.ts. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: partial_overlap
Canonical: #53599
Summary: This PR is a candidate for the same-host/node-host portion of the Chrome extension relay regression, while the canonical issue still tracks the cross-machine managed-hosting workflow and broader browser backend strategy remains adjacent.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🧂 unranked krab
Patch quality: 🧂 unranked krab
Result: blocked until real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Fix the authenticated CDP URL, page browserContextId, per-profile pairing port, WebSocket payload cap, and token comparison issues with focused tests.
  • [P1] Add redacted live Chrome extension proof that shows load, pair, tab sharing, and a browser action without the remote-debugging prompt.
  • Get maintainer/security agreement on whether the bundled first-party extension driver should land before the broader cross-machine/backend API path.

Proof guidance:

  • [P1] Needs real behavior proof before merge: Missing: the PR body explicitly says no live unpacked-extension run against real Chrome was exercised; add redacted desktop recording/screenshots, terminal output, or logs showing load, pair, tab sharing, and a browser action without the remote-debugging prompt, then update the PR body or ask a maintainer for @clawsweeper re-review. Redact private details such as IPs, tokens, phone numbers, and non-public endpoints.

Risk before merge

  • [P1] Merging changes a public browser driver value, default profile set, setup flow, and doctor migration behavior, so existing configs and upgrades need explicit compatibility acceptance.
  • [P1] The PR introduces signed-in Chrome control through a debugger extension and authenticated relay; token handling, payload bounds, tab-scope consent, and operator visibility need security review before merge.
  • [P1] The PR body explicitly says no live unpacked-extension run against real Chrome was exercised, so the popup, pairing, tab sharing, no-prompt behavior, and real Playwright connection path are unproven.
  • [P1] The canonical managed-hosting/cross-machine issue remains only partially addressed; this PR restores a loopback/node-host shape, not the original one-click remote WebSocket extension workflow.
  • [P1] Current head has failing lint/type/docs/extension/architecture/OpenGrep checks; those do not set priority by themselves, but they reinforce that the branch is not merge-ready.

Maintainer options:

  1. Fix and prove the relay before merge (recommended)
    Repair the supported CDP handshake, profile pairing, target-info contract, payload bound, and token comparison, then require a live Chrome extension run before maintainers reconsider merge.
  2. Accept the new browser-control boundary explicitly
    Maintainers may intentionally accept the bundled debugger-extension model after security review even though it expands signed-in browser control surface and setup behavior.
  3. Pause for the backend API path
    If the first-party bundled driver is the wrong long-term shape, pause or close this PR in favor of the browser backend/plugin API and cross-machine node-host work.

Next step before merge

  • [P1] Manual review is required because the external PR lacks real behavior proof, carries protected maintainer/security-boundary risk, and needs a product decision beyond the concrete code fixes.

Maintainer decision needed

  • Question: Should OpenClaw accept a bundled first-party Chrome extension relay for signed-in browser control after hardening and live proof, or defer this work to the broader browser-backend/plugin and cross-machine relay strategy?
  • Rationale: The PR restores a security-sensitive browser-control boundary, changes public config/setup behavior, and only partially covers the canonical cross-machine regression, so automation cannot choose the permanent product and trust model.
  • Likely owner: vincentkoc — They authored the relay-removal and doctor-migration PR that established the current behavior this PR reverses.
  • Options:
    • Proceed after hardening and proof (recommended): Fix the CDP auth, Playwright target info, pairing, payload, and token-compare blockers, require live Chrome extension proof, and keep the cross-machine topology tracked separately.
    • Defer to browser backend API: Pause this PR until Browser backend plugin interface (support third-party drivers via MCP/custom adapter) #19289 defines driver registration, extension ownership, and cross-machine relay boundaries.
    • Keep relay outside core: Close or replace the branch if maintainers decide signed-in Chrome extension control should live in an external plugin or platform-specific integration instead of bundled OpenClaw core.

Security
Needs attention: Needs attention: the diff adds signed-in Chrome debugger control and still has concrete relay payload-limit and token-comparison hardening gaps.

Review findings

  • [P1] Return an authenticated CDP WebSocket URL — extensions/browser/src/browser/extension-relay/relay-server.ts:123
  • [P1] Include browserContextId in page target info — extensions/browser/src/browser/extension-relay/relay-bridge.ts:342-349
  • [P2] Use the resolved relay port for pairing — extensions/browser/src/cli/browser-cli-extension.ts:32
Review details

Best possible solution:

Keep the PR open, fix the relay correctness and hardening blockers, add redacted live Chrome extension proof, and have maintainers decide whether this bundled driver should land now while cross-machine relay work stays on the linked strategy track.

Do we have a high-confidence way to reproduce the issue?

Yes, at source level for the review blockers: the relay returns an unauthenticated discovered WebSocket URL, pairing ignores resolved extension-profile ports, and synthetic page targets omit Playwright's required browserContextId. A live Chrome extension reproduction was not provided.

Is this the best way to solve the issue?

No, not yet. The direction is plausible, but this is not the best merge shape until correctness, hardening, live proof, and the first-party browser-control boundary are resolved.

Full review comments:

  • [P1] Return an authenticated CDP WebSocket URL — extensions/browser/src/browser/extension-relay/relay-server.ts:123
    /json/version is fetched through the authenticated profile URL, but this response drops the relay token. The Playwright path then derives auth headers from the returned WS URL, so discovery can succeed while the /cdp upgrade is rejected as unauthorized.
    Confidence: 0.93
  • [P1] Include browserContextId in page target info — extensions/browser/src/browser/extension-relay/relay-bridge.ts:342-349
    Playwright 1.61.1 asserts targetInfo.browserContextId for every non-browser target. The relay announces shared and newly-created tabs as page targets without that field, so connectOverCDP can throw before listing or controlling the tab. Late discovery: this bridge code was unchanged since the earlier reviewed head.
    Confidence: 0.91
    Late finding: first raised on code an earlier review cycle already covered.
  • [P2] Use the resolved relay port for pairing — extensions/browser/src/cli/browser-cli-extension.ts:32
    For unpinned extension profiles, config resolution assigns distinct ports in resolved.extensionRelayPorts, but the pairing CLI falls back to the default port. A custom driver: "extension" profile can receive a pairing string for the wrong relay.
    Confidence: 0.9
  • [P2] Set a bounded relay WebSocket payload — extensions/browser/src/browser/extension-relay/relay-server.ts:92
    ws defaults maxPayload to 100 MiB, and this relay turns accepted frames into strings for protocol parsing. Set a protocol-sized payload cap so a local or token-bearing client cannot force large memory work in the browser-control service.
    Confidence: 0.84
  • [P3] Normalize token comparison inputs — extensions/browser/src/browser/extension-relay/relay-auth.ts:62-65
    This helper still returns before timingSafeEqual when token lengths differ. At the relay auth boundary, compare fixed-length digests or canonical fixed-length buffers so mismatched candidates use the same comparison path.
    Confidence: 0.74

Overall correctness: patch is incorrect
Overall confidence: 0.91

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against be48a430bfa6.

Label changes

Label justifications:

  • P1: The PR targets a user-facing browser-control regression that blocks away-from-desk signed-in Chrome automation, but the current branch still has merge-blocking defects.
  • merge-risk: 🚨 compatibility: The diff adds a public driver: "extension" value, a built-in chrome profile, pairing commands, docs, and changed doctor migration behavior for existing extension configs.
  • merge-risk: 🚨 security-boundary: The diff introduces a Chrome debugger extension and authenticated loopback relay that can control signed-in browser tabs.
  • merge-risk: 🚨 availability: The relay WebSocket server currently inherits ws's 100 MiB maxPayload default before application parsing on a browser-control service.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🧂 unranked krab.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: Missing: the PR body explicitly says no live unpacked-extension run against real Chrome was exercised; add redacted desktop recording/screenshots, terminal output, or logs showing load, pair, tab sharing, and a browser action without the remote-debugging prompt, then update the PR body or ask a maintainer for @clawsweeper re-review. Redact private details such as IPs, tokens, phone numbers, and non-public endpoints.
Evidence reviewed

PR surface:

Source +2439, Tests +517, Docs +139, Config +3. Total +3098 across 47 files.

View PR surface stats
Area Files Added Removed Net
Source 34 2470 31 +2439
Tests 9 524 7 +517
Docs 3 143 4 +139
Config 1 4 1 +3
Generated 0 0 0 0
Other 0 0 0 0
Total 47 3141 43 +3098

Security concerns:

  • [medium] Unbounded relay WebSocket payload — extensions/browser/src/browser/extension-relay/relay-server.ts:92
    The relay constructs new WebSocketServer({ noServer: true }), inheriting ws 8.21.0's 100 MiB default maxPayload before application protocol parsing.
    Confidence: 0.84
  • [low] Length short-circuit in relay token comparison — extensions/browser/src/browser/extension-relay/relay-auth.ts:62
    extensionRelayTokenMatches exits before timingSafeEqual when candidate length differs, leaving a variable-path comparison at the relay secret boundary.
    Confidence: 0.74

What I checked:

  • Repository policy read and applied: Root AGENTS.md plus scoped docs/extensions/src/agents guides were read; their compatibility, security-boundary, dependency-contract, and real-proof requirements affected this PR review. (AGENTS.md:28, be48a430bfa6)
  • Protected live PR state: Live PR metadata shows the maintainer label plus P1 and merge-risk labels, so conservative cleanup should keep it open for explicit maintainer handling.
  • Current main still lacks the restored driver: Current main accepts only openclaw, clawd, and existing-session browser profile drivers, so this branch is not obsolete on main. (src/config/zod-schema.ts:735, be48a430bfa6)
  • Current profile-create route rejects extension: Current main's /profiles/create route rejects any profile driver outside the existing three-driver set, matching the linked regression's source-level symptom. (extensions/browser/src/browser/routes/basic.ts:397, be48a430bfa6)
  • Unauthenticated relay WebSocket URL: The relay requires auth on /cdp upgrades but returns ws://127.0.0.1:<port>/cdp from /json/version without userinfo or token, so the next CDP WebSocket connection has no auth header. (extensions/browser/src/browser/extension-relay/relay-server.ts:123, 0a59513ce119)
  • CDP caller derives auth from returned endpoint: pw-session connects to the discovered WebSocket URL and calls getHeadersWithAuth(target), so auth embedded only in the original HTTP discovery URL is not carried forward. (extensions/browser/src/browser/pw-session.ts:1058, 0a59513ce119)

Likely related people:

  • vincentkoc: Authored the merged PR that removed the legacy Chrome extension relay path and migrated legacy extension browser config to existing-session, which is the central behavior this PR reverses. (role: feature-removal and migration history owner; confidence: high; commits: 476d948732d0; files: src/config/zod-schema.ts, extensions/browser/src/browser/routes/basic.ts, src/commands/doctor/shared/legacy-config-core-normalizers.ts)
  • steipete: Has recent current-main history in the browser plugin boundary and authored the current branch's extension-relay implementation, so they are relevant for follow-up even though authorship alone is not the ownership signal. (role: recent browser/plugin-boundary contributor; confidence: medium; commits: 8eeb7f082975, 5817a76236fa, 0a59513ce119; files: extensions/browser/src/browser/config.ts, extensions/browser/src/browser/routes/basic.ts, extensions/browser/plugin-registration.ts)
  • odysseus0: Authored the adjacent merged change that dropped automatic chrome-relay profile creation before the broader extension-path removal. (role: adjacent browser profile contributor; confidence: medium; commits: b1d87370179e; files: src/browser/config.ts, src/agents/tools/browser-tool.ts, src/gateway/server/ws-connection/message-handler.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (1 earlier review cycle)
  • reviewed 2026-07-06T05:47:55.308Z sha 5348cfe :: needs real behavior proof before merge. :: [P1] Return an authenticated CDP WebSocket URL | [P2] Use the resolved relay port for pairing | [P2] Set a bounded relay WebSocket payload | [P3] Normalize token comparison inputs

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jul 6, 2026
@steipete
steipete force-pushed the claude/nostalgic-mcclintock-6d47c4 branch from 5348cfe to 0a59513 Compare July 6, 2026 07:12

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

Copy link
Copy Markdown
Contributor

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: 0a59513ce1

ℹ️ 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".

Comment on lines +342 to +346
return {
targetId,
type: "page",
title: tab.info.title,
url: tab.info.url,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Include browserContextId in synthetic page targets

When a shared tab is auto-attached, this targetInfo is sent in Target.attachedToTarget. The repo pins [email protected], whose CRBrowser._onAttachedToTarget asserts targetInfo.browserContextId for every non-browser target before falling back to the default context, so the extension relay throws as soon as Playwright sees a shared or newly-created tab instead of listing/controlling it. Add a stable non-empty browserContextId to the synthetic page target info so the CDP contract matches Playwright's connectOverCDP expectations.

Useful? React with 👍 / 👎.

@steipete
steipete force-pushed the claude/nostalgic-mcclintock-6d47c4 branch from 0a59513 to 2544feb Compare July 6, 2026 08:04

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

Copy link
Copy Markdown
Contributor

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: 2544febaa6

ℹ️ 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".

Comment on lines +283 to +286
const existing = this.tabs.get(info.tabId);
if (existing) {
existing.info = info;
} else {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Reattach existing tabs after extension reconnect

When the extension socket reconnects while a CDP client is still connected, handleExtensionGone() clears each tab's attached state but intentionally keeps the tab entries. The next hello/tabs sync for the same shared tab takes this existing branch and only updates metadata, so auto-attach clients never get a new Target.attachedToTarget for the tab they just saw detached; the active Playwright connection can remain alive with no pages until the tab is removed/re-shared or the client reconnects. Re-run the auto-attach path when an existing tab is currently unattached and any client has autoAttach enabled.

Useful? React with 👍 / 👎.

Comment on lines +379 to +384
case "unpair": {
await chrome.storage.local.remove(["relayUrl", "token"]);
relayWs?.close();
relayWs = null;
setBadge("off");
sendResponse({ ok: true });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Detach debugger sessions when unpairing

If the user clicks Unpair while any shared tab is attached, this only removes the relay credentials and closes the WebSocket; it never calls detachDebugger() for the attachedTabs set or removes the tabs from the OpenClaw group. Since the debugger attachment is independent of the relay socket, Chrome can continue showing the debugging infobar and the extension keeps those tabs attached after the user has explicitly revoked pairing. Detach all attached tabs (and ideally clear/revoke the shared group) before returning success.

Useful? React with 👍 / 👎.

@steipete
steipete force-pushed the claude/nostalgic-mcclintock-6d47c4 branch from 2544feb to 99f70b2 Compare July 6, 2026 08:18
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Update: remote-node support + live end-to-end proof + dedupe

Remote browser nodes now supported. The relay auth token is now a
host-local secret (created on first use under credentials/, mode 0600)
instead of being derived from gateway auth. Each machine that runs a browser —
the gateway host and every browser node host — owns its own token, so the
extension pairs with whichever machine hosts its Chrome and no gateway
credential travels to a node. The node host already runs the shared
browser-control bootstrap, so this was the only missing piece; it also removes
the "relay can't start until gateway auth exists" failure mode. Docs gained a
Remote browser nodes section. This closes the cross-machine gap for #53599.

Live end-to-end (real Chrome). Loaded the built unpacked extension into a
real Chrome (Chrome for Testing, v149 — stable Chrome 137+ removed
--load-extension), paired it through the real popup UI, then drove a tab
through the extension's real chrome.debugger path. All checks passed:

  • extension connected to the relay and reported Chrome/149.0.0.0;
  • an agent-opened tab landed in the orange "OpenClaw" tab group (the consent
    boundary);
  • chrome.debugger attached with a real CDP targetId — no "Allow remote
    debugging?" prompt
    ;
  • CDP Runtime.evaluate forwarded through the extension returned the live page
    content (OpenClaw E2E|relay-works), confirmed by screenshot.

This complements the unit tests, which already verify the relay bridge's CDP
target synthesis against real Playwright connectOverCDP. Full suite:
pnpm test extensions/browser → 1467 passing; tsgo:core/:extensions/:core:test
green; format/lint clean.

Dedupe. #53599 is the canonical tracking issue (referenced); #55840 (closed
not-planned) is a duplicate "bring back the relay" request. No open PR restores
driver: "extension" — this PR is novel. Coordination note: open PR #93680
("My Browser node-anchored turn routing", stacked behind #93411 and stale) also
adds an MV3 extension + loopback CDP bridge, but for a different feature
(side-panel copilot) on a separate driver: "extension-bridge" literal under
extensions/chrome-extension/. No literal collision with this PR's
driver: "extension", but the two share ~9 edited files (the browser driver zod
union, profile capabilities, server-context); whoever lands second rebases.

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

Copy link
Copy Markdown
Contributor

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: 99f70b22cd

ℹ️ 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".


async function findOpenClawGroups() {
try {
return await chrome.tabGroups.query({ title: OPENCLAW_TAB_GROUP_TITLE });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Track extension-created groups before sharing tabs

When a user already has, or manually renames, a Chrome tab group to OpenClaw, this title-only lookup treats every tab in that group as shared immediately on hello/tabs sync, before the user clicks the extension's share button or drags tabs into a group created by the extension. Because listSharedTabs() sends all groups returned here to the relay, pairing the extension can expose unrelated tabs to agents; persist and check group ids created by the extension, or use another non-colliding marker, instead of trusting the display title alone.

Useful? React with 👍 / 👎.

steipete added 6 commits July 6, 2026 01:32
…on relay

Reintroduces browser profile driver "extension" (removed in 2026.3.22) as a
loopback relay that drives the user's signed-in Chrome through an MV3 extension
instead of the remote-debugging port. This avoids Chrome's blocking "Allow
remote debugging?" prompt, which cannot be clicked when the operator drives
OpenClaw from a phone. Automated tabs live in an "OpenClaw" tab group (the
consent boundary), mirroring the Codex/Claude-in-Chrome model.

- relay bridge synthesizes the CDP browser target surface for Playwright
  connectOverCDP and forwards session-scoped commands to chrome.debugger
- relay server binds loopback only; both sides authenticate with a token
  derived (HMAC-SHA256) from gateway auth, so the raw credential never reaches
  Chrome; extension origin + loopback Host checks guard the upgrade
- built-in "chrome" profile; distinct relay ports per extension profile;
  relay reconciles on auth rotation / cdpPort change and prunes removed profiles
- doctor + status surface the extension transport; doctor keeps repairing the
  retired relay endpoint URL on legacy "extension" profiles

Refs #53599
Thin MV3 extension (chrome-extension/): a WebSocket client to the loopback
relay plus chrome.debugger forwarding and OpenClaw tab-group management. All
CDP target synthesis lives server-side in the relay bridge, so the extension
stays a dumb transport (the removed 2026.3 extension put that logic in a
1000-line untestable service worker). Popup handles pairing and per-tab share
toggle; `openclaw browser extension path|pair` load and pair it. A build copy
hook stages it into dist so the load path is stable.

Refs #53599
Adds docs/tools/chrome-extension for the restored extension driver (install,
pair, tab-group consent model, security posture) and wires it into the browser
docs profile section and nav.

Refs #53599
Derives the relay auth token from a host-local secret in the credentials dir
(created on first use) instead of gateway auth. Each machine that runs a
browser — the gateway host and every browser node host — owns its own token, so
the extension pairs with whichever machine hosts its Chrome and no gateway
credential travels to a node. The node host already runs the shared browser
control bootstrap, so this is all that was missing for cross-machine control.

Also removes the "relay needs gateway auth before it can start" failure mode:
startup and `openclaw browser extension pair` ensure the secret exists.

Refs #53599
- Make the host-local relay secret creation atomic (O_CREAT|O_EXCL + adopt the
  winner on EEXIST) so the gateway service and `extension pair` CLI cannot mint
  divergent tokens on a fresh host (would 401 until restart); credentials dir
  created mode 0700. (adversarial review finding)
- Resolve type-aware oxlint findings across the relay + extension: unknown catch
  vars, addEventListener over ws.on* in the MV3 worker, void async listeners,
  drop useless returns/spreads, Object.assign over map-spread, safe ws frame
  decode (Buffer[]/ArrayBuffer), toSorted.
- Add extensionRelayDefaultPort/extensionRelayPorts to remaining test config
  literals; type the extension relay-core module (.d.ts, excluded from dist);
  regenerate docs_map.
- Hash both operands before timingSafeEqual so token comparison has no
  length short-circuit (GHSA-jj6q-rrrf-h66h).
- Bound the relay WebSocketServer with maxPayload (64 MiB, headroom for CDP
  screenshots/bodies) against oversized frames (GHSA-vw3h-q6xq-jjm5).
- Rewrite the config test env helper to avoid the skill-env-host-injection
  shape (GHSA-82g8-464f-2mv7); it is a test-only env swap.
@steipete
steipete force-pushed the claude/nostalgic-mcclintock-6d47c4 branch from 99f70b2 to a98e5b5 Compare July 6, 2026 08:34

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

Copy link
Copy Markdown
Contributor

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: a98e5b58a0

ℹ️ 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".

}
if (req.method === "GET" && (path === "/json" || path === "/json/list")) {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(bridge.sharedTabs()));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Return CDP-shaped targets from /json/list

When target-id probing falls back to /json/list (for example after Target.getTargetInfo fails on the extension CDP path), existing fallback code expects Chrome-style entries with id/url (extensions/browser/src/browser/pw-session.ts:1203-1210, and JSON tab consumers read t.id). This endpoint currently serializes RelayTabInfo (tabId, url, title, active), so multiple shared tabs cannot be resolved or listed through the relay in that fallback path. Return Chrome-compatible target objects with a stable id (and type/webSocketDebuggerUrl where applicable) instead of the extension wire shape.

Useful? React with 👍 / 👎.

pending.reject(new Error("extension relay stopped"));
}
this.pendingExtension.clear();
this.extension?.socket.close(1001, "relay stopped");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Detach tabs before stopping the relay

If OpenClaw stops or rebinds the relay while a CDP client has attached tabs, stopExtensionRelays() reaches this dispose() path, which closes the extension socket and clears bridge state without sending any detach commands. The extension's WebSocket close handler only schedules reconnect and keeps its attachedTabs, so Chrome can continue showing the debugger infobar and the tabs remain debugger-attached after OpenClaw has stopped. Detach all attached tabs before closing the extension socket, or make the extension detach on relay shutdown/close.

Useful? React with 👍 / 👎.

@steipete
steipete merged commit d6801f2 into main Jul 6, 2026
99 checks passed
@steipete
steipete deleted the claude/nostalgic-mcclintock-6d47c4 branch July 6, 2026 09:08
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 7, 2026
…sion relay (no remote-debugging prompt) (openclaw#100619)

* feat(browser): restore driver "extension" via loopback Chrome extension relay

Reintroduces browser profile driver "extension" (removed in 2026.3.22) as a
loopback relay that drives the user's signed-in Chrome through an MV3 extension
instead of the remote-debugging port. This avoids Chrome's blocking "Allow
remote debugging?" prompt, which cannot be clicked when the operator drives
OpenClaw from a phone. Automated tabs live in an "OpenClaw" tab group (the
consent boundary), mirroring the Codex/Claude-in-Chrome model.

- relay bridge synthesizes the CDP browser target surface for Playwright
  connectOverCDP and forwards session-scoped commands to chrome.debugger
- relay server binds loopback only; both sides authenticate with a token
  derived (HMAC-SHA256) from gateway auth, so the raw credential never reaches
  Chrome; extension origin + loopback Host checks guard the upgrade
- built-in "chrome" profile; distinct relay ports per extension profile;
  relay reconciles on auth rotation / cdpPort change and prunes removed profiles
- doctor + status surface the extension transport; doctor keeps repairing the
  retired relay endpoint URL on legacy "extension" profiles

Refs openclaw#53599

* feat(browser): bundle the OpenClaw MV3 Chrome extension

Thin MV3 extension (chrome-extension/): a WebSocket client to the loopback
relay plus chrome.debugger forwarding and OpenClaw tab-group management. All
CDP target synthesis lives server-side in the relay bridge, so the extension
stays a dumb transport (the removed 2026.3 extension put that logic in a
1000-line untestable service worker). Popup handles pairing and per-tab share
toggle; `openclaw browser extension path|pair` load and pair it. A build copy
hook stages it into dist so the load path is stable.

Refs openclaw#53599

* docs(browser): document the Chrome extension profile

Adds docs/tools/chrome-extension for the restored extension driver (install,
pair, tab-group consent model, security posture) and wires it into the browser
docs profile section and nav.

Refs openclaw#53599

* feat(browser): make the extension relay work on remote browser nodes

Derives the relay auth token from a host-local secret in the credentials dir
(created on first use) instead of gateway auth. Each machine that runs a
browser — the gateway host and every browser node host — owns its own token, so
the extension pairs with whichever machine hosts its Chrome and no gateway
credential travels to a node. The node host already runs the shared browser
control bootstrap, so this is all that was missing for cross-machine control.

Also removes the "relay needs gateway auth before it can start" failure mode:
startup and `openclaw browser extension pair` ensure the secret exists.

Refs openclaw#53599

* fix(browser): harden relay secret creation and satisfy CI lint/typecheck

- Make the host-local relay secret creation atomic (O_CREAT|O_EXCL + adopt the
  winner on EEXIST) so the gateway service and `extension pair` CLI cannot mint
  divergent tokens on a fresh host (would 401 until restart); credentials dir
  created mode 0700. (adversarial review finding)
- Resolve type-aware oxlint findings across the relay + extension: unknown catch
  vars, addEventListener over ws.on* in the MV3 worker, void async listeners,
  drop useless returns/spreads, Object.assign over map-spread, safe ws frame
  decode (Buffer[]/ArrayBuffer), toSorted.
- Add extensionRelayDefaultPort/extensionRelayPorts to remaining test config
  literals; type the extension relay-core module (.d.ts, excluded from dist);
  regenerate docs_map.

* fix(browser): satisfy OpenGrep security policy on the relay

- Hash both operands before timingSafeEqual so token comparison has no
  length short-circuit (GHSA-jj6q-rrrf-h66h).
- Bound the relay WebSocketServer with maxPayload (64 MiB, headroom for CDP
  screenshots/bodies) against oversized frames (GHSA-vw3h-q6xq-jjm5).
- Rewrite the config test env helper to avoid the skill-env-host-injection
  shape (GHSA-82g8-464f-2mv7); it is a test-only env swap.
giodl73-repo pushed a commit to giodl73-repo/openclaw that referenced this pull request Jul 8, 2026
…sion relay (no remote-debugging prompt) (openclaw#100619)

* feat(browser): restore driver "extension" via loopback Chrome extension relay

Reintroduces browser profile driver "extension" (removed in 2026.3.22) as a
loopback relay that drives the user's signed-in Chrome through an MV3 extension
instead of the remote-debugging port. This avoids Chrome's blocking "Allow
remote debugging?" prompt, which cannot be clicked when the operator drives
OpenClaw from a phone. Automated tabs live in an "OpenClaw" tab group (the
consent boundary), mirroring the Codex/Claude-in-Chrome model.

- relay bridge synthesizes the CDP browser target surface for Playwright
  connectOverCDP and forwards session-scoped commands to chrome.debugger
- relay server binds loopback only; both sides authenticate with a token
  derived (HMAC-SHA256) from gateway auth, so the raw credential never reaches
  Chrome; extension origin + loopback Host checks guard the upgrade
- built-in "chrome" profile; distinct relay ports per extension profile;
  relay reconciles on auth rotation / cdpPort change and prunes removed profiles
- doctor + status surface the extension transport; doctor keeps repairing the
  retired relay endpoint URL on legacy "extension" profiles

Refs openclaw#53599

* feat(browser): bundle the OpenClaw MV3 Chrome extension

Thin MV3 extension (chrome-extension/): a WebSocket client to the loopback
relay plus chrome.debugger forwarding and OpenClaw tab-group management. All
CDP target synthesis lives server-side in the relay bridge, so the extension
stays a dumb transport (the removed 2026.3 extension put that logic in a
1000-line untestable service worker). Popup handles pairing and per-tab share
toggle; `openclaw browser extension path|pair` load and pair it. A build copy
hook stages it into dist so the load path is stable.

Refs openclaw#53599

* docs(browser): document the Chrome extension profile

Adds docs/tools/chrome-extension for the restored extension driver (install,
pair, tab-group consent model, security posture) and wires it into the browser
docs profile section and nav.

Refs openclaw#53599

* feat(browser): make the extension relay work on remote browser nodes

Derives the relay auth token from a host-local secret in the credentials dir
(created on first use) instead of gateway auth. Each machine that runs a
browser — the gateway host and every browser node host — owns its own token, so
the extension pairs with whichever machine hosts its Chrome and no gateway
credential travels to a node. The node host already runs the shared browser
control bootstrap, so this is all that was missing for cross-machine control.

Also removes the "relay needs gateway auth before it can start" failure mode:
startup and `openclaw browser extension pair` ensure the secret exists.

Refs openclaw#53599

* fix(browser): harden relay secret creation and satisfy CI lint/typecheck

- Make the host-local relay secret creation atomic (O_CREAT|O_EXCL + adopt the
  winner on EEXIST) so the gateway service and `extension pair` CLI cannot mint
  divergent tokens on a fresh host (would 401 until restart); credentials dir
  created mode 0700. (adversarial review finding)
- Resolve type-aware oxlint findings across the relay + extension: unknown catch
  vars, addEventListener over ws.on* in the MV3 worker, void async listeners,
  drop useless returns/spreads, Object.assign over map-spread, safe ws frame
  decode (Buffer[]/ArrayBuffer), toSorted.
- Add extensionRelayDefaultPort/extensionRelayPorts to remaining test config
  literals; type the extension relay-core module (.d.ts, excluded from dist);
  regenerate docs_map.

* fix(browser): satisfy OpenGrep security policy on the relay

- Hash both operands before timingSafeEqual so token comparison has no
  length short-circuit (GHSA-jj6q-rrrf-h66h).
- Bound the relay WebSocketServer with maxPayload (64 MiB, headroom for CDP
  screenshots/bodies) against oversized frames (GHSA-vw3h-q6xq-jjm5).
- Rewrite the config test env helper to avoid the skill-env-host-injection
  shape (GHSA-82g8-464f-2mv7); it is a test-only env swap.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling commands Command implementations docs Improvements or additions to documentation maintainer Maintainer-authored PR merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. P1 High-priority user-facing bug, regression, or broken workflow. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: XL status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants