Repository navigation
feat(gateway): relay a website browser's CDP addresses at /browser/<key>/cdp/<token>/ - #500
Conversation
…ey>/cdp/<token>/ The gateway finds the website browser behind a /svc child from the child's card and relays its CDP discovery and WebSocket both ways, each leg pinged every 20 s. Every address the browser hands out, in json/version and json/list or in its cdp answer on the /svc route, comes back at the gateway: everything before /cdp/<token>/ becomes the address the caller reached the gateway at and /browser/<key>, so a client following one stays on the gateway. Each connection is a browser_connection trajectory event, attributed by role and without its token, which uvicorn's and httpx's log lines don't show either. The gateway image gains websockets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oth sides with 1011 and records why A send that raised used to vanish into the relay's gather, and the event said the gateway closed it with 1001. Now both sides are closed as 1011 and the event and a warning carry the failure's type, never its message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… which follows the client's close Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
🤖 Automated Claude routine — Env Pod PR Context Review. This is a bot, not Edgar Arakelyan; not a human review. Context from a sibling PR rather than from this diff: #443 is also open against
Neither is on Two smaller things from the same comparison, if they aren't already deliberate:
Trusting Generated by Claude Code |
A computer lists an app whose server isn't answering by its name alone, so a first read in that window cached "no browser" for the gateway's life: /browser/<key> answered 404 and the cdp answer went through unrewritten. Now a card naming none is read again next time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s found once up, and a dying app server or driver closes a live connection Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… didn't start Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What and why
The gateway gets a route,
/browser/<key>/cdp/<token>/…, that relays a website browser's CDP addresses. It covers discovery (json/version,json/list) and the WebSocket, in both directions. Today a CDP address is the browser's own address, so an agent can only use it if it can reach the browser's host directly. In a multi env, agents reach everything through the gateway's one address. This is FD-4958 (M8.11). Its scope and six decisions are recorded there, along with a diagram of the discovery options.What the gateway does (new
env/gateway/browser_proxy.py, wired intogateway.py):/svcchild is a browser when its card, or one of its child cards, offersbrowser/v1cdpand opens/cdp/to agents incomputer-app/v1'sagent_routes. A browser computer's chromium app is one./browser/<key>/…forwards to<child>/svc/<app>/cdp/<token>/….cdpanswer (decisions 2, 3 and 6).json/version's andjson/list's address fields, and thehttp_urlandws_urlthatbrowser/v1 cdpreturns through/svc/<key>/./cdp/<token>/becomes the address the caller reached the gateway at, plus/browser/<key>. That address is theHostit sent, checked to be a host and port, with the scheme fromX-Forwarded-Protowhen a tunnel sets it.se:cdpto the Grid's address, and Browserless rewrites discovery fromEXTERNAL._both_ways.ws_ping_intervalon the client side and thewebsocketsclient upstream.browser_connectionevent when the connection ends.AgentEnv-Role, as for tool calls, sodefaultfor most CDP clients; decision 5),opened_at,closed_at,close_code,closed_by(client, browser or gateway), andstatusfor a refusal.websockets. Without a WebSocket library, uvicorn can't accept an upgrade. The image is published by source digest, so the next release publishes a new one, and the hub picks it up through the gateway's promotion path (FD-4549).Not in this PR:
rewrite_child_carddrops a child's nested cards today, sobrowser_connect_cdpcan't find a browser in a multi env yet;Docs:
AGENTS.md's code map describes the route beside the gateway's site route.Also:
cdp_clients, the Playwright and Puppeteer fixture, moves frombrowser_computer_test.pytotst/integration/computer/conftest.py, since the new test uses it too.How it was tested
ruff check: clean.Unit tests (
tst/unit/env/gateway/browser_route_test.py): the real gateway app on loopback, in front of a stand-in browser computer that serves a driver-shaped card, acdpanswer minted at another host, discovery and a WebSocket. They check:cdpanswer,json/versionandjson/listall name the gateway, andwsscomes back whenX-Forwarded-Proto: https;/svctraffic goes through untouched;Hostvalues refused), log redaction, and the mirrored names againstagentenv_protocol's.With the rest of the gateway suite: 760 passed.
Integration (new
tst/integration/computer/browser_gateway_test.py): the gateway image built from its folder and run as a container, with agentenv's chromium, deployed alone as a browser computer, as its/svcchild.http_url, through discovery, and Puppeteer and Playwright byws_urleach open a site through the gateway.browser_connectionevents, closed by the client, plus one refusal with status 403. Neither they nordocker logscontain the token.Browser.getVersionafter each./browser/web/…answers 404, and once the server is back the same request answers with the address at the gateway. Against the proxy befored1933e25, this test fails: the second request still gets a 404.VMs (agent-environments-admin, amd64 c6i.2xlarge and arm64 c7g.2xlarge):
dd27a3da: gateway unit tests 760/760 on both arches. Integration (browser_gateway_test.py's 3 tests, all ofbrowser_computer_test.py, the idle probe) 12/12 on amd64. On arm64, 9/9 without the 3 echo-agent tests, which need qemu and passed with it ate99a3074.e99a3074.browser_gateway_test.pyand all ofbrowser_computer_test.py, for the moved fixture, 10/10 on amd64. On arm64, likewise 10/10: the 3 echo-agent tests need qemu there, becausebuild_or_reusebuilds their image for amd64, and they passed once it was installed.Gatewaykeeps its trajectory file open until the process exits. That's existing behaviour, not something this PR adds. CI runs the suite across workers.Size: 777 lines added, about 400 of them tests.
🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge; no blocking findings remain.Summary
Adds a gateway route for browser CDP discovery and WebSocket connections.
No new findings. The changes since the previous review are empty, and the full-PR rules check found no violations. The earlier, unnumbered finding about hidden relay errors is fixed:
_both_wayschecks failed tasks, closes both connections with 1011, and records only the error type.Diagram
sequenceDiagram participant Client participant Gateway participant Browser Client->>Gateway: Request CDP address through /svc/key/ Gateway->>Browser: Forward request Browser-->>Gateway: Browser CDP addresses Gateway-->>Client: Addresses at /browser/key/cdp/token/ Client->>Gateway: Request discovery or open WebSocket Gateway->>Browser: Forward to the browser's CDP route Browser-->>Gateway: Discovery answer or WebSocket messages Gateway-->>Client: Rewritten discovery or relayed messages Note over Gateway: Record connection outcome without the tokenReviews (7) · Last reviewed commit: "ci: run the Local backends workflow on t..." · Reviewed by Greptile