Repository navigation
feat(python): extension_cdp_url for local_browser.connect - #3252
Merged
Merged
Conversation
## Why
Stagehand's extension opens its own CDP connection to the browser, and
it is handed the URL the Python client connected with. That only works
when the client and the browser can use the same address.
Sometimes they can't. Our case: the client runs in a server process and
reaches a sandboxed Chrome through an authenticated gateway,
`wss://<gateway>/.../cdp?api_key=...`. The extension, running inside
that Chrome, should dial the browser's local endpoint
(`ws://127.0.0.1:<port>/devtools/browser/<id>`). Using the gateway URL
would hand the gateway's credentials to the browser, which loads
arbitrary pages, and send every extension command out and back in
through the gateway. Today the only workaround is overriding the private
`Stagehand._worker_init_params`.
## What
```python
browser = await local_browser.connect(
cdp_url="wss://gateway/cdp?api_key=...",
extension_cdp_url="ws://127.0.0.1:9300/devtools/browser/<id>",
)
stagehand = await Stagehand.create(browser=browser)
```
- `local_browser.connect` takes an optional `extension_cdp_url`, which
must be a `ws://` or `wss://` URL with a host.
- It is carried on the claimed browser's worker metadata, and
`Stagehand.create` sends it as the init param `browser_cdp_url` in place
of the client's URL.
- When it is unset, behaviour is unchanged.
- Python SDK only, with a minor changeset. The TypeScript SDK has the
same assumption and could take the same option; happy to follow up.
## Tests
- New tests cover the init param, connect carrying the value through,
the default, and rejection of non-WebSocket URLs.
- `ruff format --check`, `ruff check`, `ty check` and `generate.py
--check` pass.
- `pytest`: 793 passed.
`test_official_api_routes_through_and_never_closes_a_caller_http_client`
fails with and without this change in my checkout, because it needs the
extension built in `packages/extension/dist`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds an optional `extension_cdp_url` to `local_browser.connect` so
Stagehand's extension can dial a different CDP WebSocket than the
client's, for cases where the client reaches Chrome through a proxy the
extension cannot use.
- When set, Stagehand sends it as the init param `browser_cdp_url` in
place of the client's URL; when unset, behavior is unchanged.
- The URL must be `ws://` or `wss://` with a host; anything else raises
a validation error.
- Python SDK only; the TypeScript SDK has the same assumption and could
take the same option in a follow-up.
- New tests cover the init param, carrying the value through, the
default, and rejection of invalid URLs. `pytest` reports 793 passed;
`test_official_api_routes_through_and_never_closes_a_caller_http_client`
fails with and without this change because it needs the extension built
in `packages/extension/dist`.
<sup>Written for commit c119e53.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/browserbase/stagehand/pull/3223?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="View guided diff"
src="https://www.cubic.dev/buttons/review-in-cubic-light.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
Co-authored-by: Sydney Runkle <sydney@langchain.dev>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Sean McGuire <75873287+seanmcguire12@users.noreply.github.com>
🦋 Changeset detectedLatest commit: 48a3789 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor
There was a problem hiding this comment.
All reported issues were addressed
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
Contributor
There was a problem hiding this comment.
All reported issues were addressed across 8 files
Architecture diagram
sequenceDiagram
participant Client as Python Client
participant Connect as local_browser.connect()
participant Validate as URL Validator
participant Browser as StagehandBrowser
participant Stagehand as Stagehand.create()
participant Ext as Browser Extension
Note over Client,Ext: Browser Connection with Optional Extension CDP URL
Client->>Connect: connect(cdp_url, extension_cdp_url?)
alt extension_cdp_url provided
Client->>Validate: Validate extension_cdp_url
alt Valid ws:// or wss:// URL
Validate-->>Connect: Valid URL
else Invalid URL format
Validate-->>Connect: ValidationError
Connect-->>Client: Raise error
end
Connect->>Browser: Attach metadata (browser_cdp_url = extension_cdp_url)
else No extension_cdp_url
Connect->>Browser: Attach metadata (browser_cdp_url = None)
end
Client->>Stagehand: Stagehand.create(browser)
alt browser_cdp_url is set
Stagehand->>Ext: Send browser_cdp_url as init param
Note over Stagehand,Ext: Extension dials local CDP endpoint
else browser_cdp_url is None
Stagehand->>Ext: Use client's web_socket_debugger_url
Note over Stagehand,Ext: Extension uses same URL as client
end
Ext->>Ext: Connect to CDP WebSocket
Ext-->>Stagehand: Extension ready
Stagehand-->>Client: Stagehand instance
Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
View guided diff | Turn on auto-fix | Re-trigger cubic
miguelg719
approved these changes
Oct 19, 2026
miguelg719
pushed a commit
that referenced
this pull request
Oct 20, 2026
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @browserbasehq/stagehand@4.3.0 ### Minor Changes - [#3267](#3267) [`cb9a1d1`](cb9a1d1) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add extension archive getter - [#3253](#3253) [`6bf4146`](6bf4146) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add optional `extensionCdpUrl` to `localBrowser.connect` to allow configuring the in-browser CDP url - [#3263](#3263) [`ecb2a6b`](ecb2a6b) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add `headers` to `localBrowser.connect` so CDP gateways can be authenticated without credentials in the URL - [#3183](#3183) [`901eb27`](901eb27) Thanks [@supremeboxlogos](https://github.com/supremeboxlogos)! - Add `page.waitForFunction()` to wait until a predicate in the page returns a truthy value. ### Patch Changes - [#3229](#3229) [`c6b6f27`](c6b6f27) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - skip caching for request bodies larger than 25 MB. - [#3198](#3198) [`10418f9`](10418f9) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - enable WebMCP by default for local chromium versions newer than 157 - [#3210](#3210) [`1c40ba7`](1c40ba7) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Require client transport for Browserbase Model Gateway and cache requests. - [#3209](#3209) [`17de6ea`](17de6ea) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Route Browserbase Model Gateway and cache requests through the connected SDK. ## @browserbasehq/eve@0.2.2 ### Patch Changes - Updated dependencies [[`c6b6f27`](c6b6f27), [`cb9a1d1`](cb9a1d1), [`10418f9`](10418f9), [`1c40ba7`](1c40ba7), [`17de6ea`](17de6ea), [`6bf4146`](6bf4146), [`ecb2a6b`](ecb2a6b), [`901eb27`](901eb27)]: - @browserbasehq/stagehand@4.3.0 ## @browserbasehq/stagehand-protocol@3.0.0 ### Major Changes - [#3210](#3210) [`1c40ba7`](1c40ba7) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Require client transport for Browserbase Model Gateway and cache requests. ### Minor Changes - [#3208](#3208) [`4a7c637`](4a7c637) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Support routing Browserbase Model Gateway and cache requests through the connected SDK. - [#3183](#3183) [`901eb27`](901eb27) Thanks [@supremeboxlogos](https://github.com/supremeboxlogos)! - Add the `page.wait_for_function` method. ## @browserbasehq/stagehand-extension@1.2.0 ### Minor Changes - [#3208](#3208) [`4a7c637`](4a7c637) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Support routing Browserbase Model Gateway and cache requests through the connected SDK. - [#3183](#3183) [`901eb27`](901eb27) Thanks [@supremeboxlogos](https://github.com/supremeboxlogos)! - Add the `page.wait_for_function` method. ### Patch Changes - [#3229](#3229) [`c6b6f27`](c6b6f27) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - skip caching for request bodies larger than 25 MB. - [#3257](#3257) [`113ad81`](113ad81) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - honour a changed extension CDP WS URL when creating a new Stagehand instance after closing the previous one - [#3210](#3210) [`1c40ba7`](1c40ba7) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Require client transport for Browserbase Model Gateway and cache requests. ## @browserbasehq/stagehand-go@4.3.0 ### Minor Changes - [#3267](#3267) [`cb9a1d1`](cb9a1d1) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add extension archive getter - [#3265](#3265) [`a31e2da`](a31e2da) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add `Headers` to `LocalBrowserConnectOptions` so CDP gateways can be authenticated without credentials in the URL - [#3254](#3254) [`9eac212`](9eac212) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add optional `ExtensionCDPURL` to `LocalBrowserConnectOptions` to allow configuring the in-browser CDP url. - [#3183](#3183) [`901eb27`](901eb27) Thanks [@supremeboxlogos](https://github.com/supremeboxlogos)! - Add `Page.WaitForFunction` to wait until a predicate in the page returns a truthy value. ### Patch Changes - [#3229](#3229) [`c6b6f27`](c6b6f27) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - skip caching for request bodies larger than 25 MB. - [#3198](#3198) [`10418f9`](10418f9) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - enable WebMCP by default for local chromium versions newer than 157 - [#3210](#3210) [`1c40ba7`](1c40ba7) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Require client transport for Browserbase Model Gateway and cache requests. - [#3209](#3209) [`17de6ea`](17de6ea) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Route Browserbase Model Gateway and cache requests through the connected SDK. ## @browserbasehq/stagehand-python@4.3.0 ### Minor Changes - [#3260](#3260) [`2859fd5`](2859fd5) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add `headers` to `local_browser.connect` so CDP gateways can be authenticated without credentials in the URL - [#3267](#3267) [`cb9a1d1`](cb9a1d1) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add extension archive getter - [#3252](#3252) [`21e0363`](21e0363) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add `extension_cdp_url` to `local_browser.connect` for clients that reach Chrome through a proxy the extension cannot use - [#3183](#3183) [`901eb27`](901eb27) Thanks [@supremeboxlogos](https://github.com/supremeboxlogos)! - Add `page.wait_for_function()` to wait until a predicate in the page returns a truthy value. ### Patch Changes - [#3229](#3229) [`c6b6f27`](c6b6f27) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - skip caching for request bodies larger than 25 MB. - [#3198](#3198) [`10418f9`](10418f9) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - enable WebMCP by default for local chromium versions newer than 157 - [#3210](#3210) [`1c40ba7`](1c40ba7) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Require client transport for Browserbase Model Gateway and cache requests. - [#3209](#3209) [`17de6ea`](17de6ea) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Route Browserbase Model Gateway and cache requests through the connected SDK. ## @browserbasehq/stagehand-examples@0.0.3 ### Patch Changes - Updated dependencies [[`c6b6f27`](c6b6f27), [`cb9a1d1`](cb9a1d1), [`10418f9`](10418f9), [`1c40ba7`](1c40ba7), [`17de6ea`](17de6ea), [`6bf4146`](6bf4146), [`ecb2a6b`](ecb2a6b), [`901eb27`](901eb27)]: - @browserbasehq/stagehand@4.3.0 - @browserbasehq/stagehand-integrations@4.0.6 ## @browserbasehq/stagehand-integrations-claude-agent-sdk@4.0.5 ### Patch Changes - Updated dependencies []: - @browserbasehq/stagehand-integrations@4.0.6 ## @browserbasehq/stagehand-integrations-example-claude-code-facade@4.0.6 ### Patch Changes - Updated dependencies []: - @browserbasehq/stagehand-integrations@4.0.6 - @browserbasehq/stagehand-integrations-claude-agent-sdk@4.0.5 ## @browserbasehq/stagehand-integrations-claude-cua-sdk@4.0.4 ### Patch Changes - Updated dependencies [[`c6b6f27`](c6b6f27), [`cb9a1d1`](cb9a1d1), [`10418f9`](10418f9), [`1c40ba7`](1c40ba7), [`17de6ea`](17de6ea), [`6bf4146`](6bf4146), [`ecb2a6b`](ecb2a6b), [`901eb27`](901eb27)]: - @browserbasehq/stagehand@4.3.0 - @browserbasehq/stagehand-integrations@4.0.6 ## @browserbasehq/stagehand-integrations-example-codex-facade@4.0.6 ### Patch Changes - Updated dependencies []: - @browserbasehq/stagehand-integrations@4.0.6 - @browserbasehq/stagehand-integrations-codex-sdk@4.0.5 ## @browserbasehq/stagehand-integrations-codex-sdk@4.0.5 ### Patch Changes - Updated dependencies []: - @browserbasehq/stagehand-integrations@4.0.6 ## @browserbasehq/stagehand-integrations@4.0.6 ### Patch Changes - Updated dependencies [[`c6b6f27`](c6b6f27), [`cb9a1d1`](cb9a1d1), [`10418f9`](10418f9), [`1c40ba7`](1c40ba7), [`17de6ea`](17de6ea), [`6bf4146`](6bf4146), [`ecb2a6b`](ecb2a6b), [`901eb27`](901eb27)]: - @browserbasehq/stagehand@4.3.0 ## @browserbasehq/stagehand-integrations-example-cursor-facade@4.0.4 ### Patch Changes - Updated dependencies []: - @browserbasehq/stagehand-integrations@4.0.6 - @browserbasehq/stagehand-integrations-cursor-sdk@4.0.5 ## @browserbasehq/stagehand-integrations-cursor-sdk@4.0.5 ### Patch Changes - Updated dependencies []: - @browserbasehq/stagehand-integrations@4.0.6 ## @browserbasehq/stagehand-integrations-deepagents-sdk@4.0.5 ### Patch Changes - Updated dependencies []: - @browserbasehq/stagehand-integrations@4.0.6 ## @browserbasehq/stagehand-integrations-eve-sdk@4.0.5 ### Patch Changes - Updated dependencies []: - @browserbasehq/stagehand-integrations@4.0.6 ## @browserbasehq/stagehand-integrations-fx-sdk@4.0.5 ### Patch Changes - Updated dependencies []: - @browserbasehq/stagehand-integrations@4.0.6 ## @browserbasehq/stagehand-integrations-gemini-cua-sdk@4.0.4 ### Patch Changes - Updated dependencies [[`c6b6f27`](c6b6f27), [`cb9a1d1`](cb9a1d1), [`10418f9`](10418f9), [`1c40ba7`](1c40ba7), [`17de6ea`](17de6ea), [`6bf4146`](6bf4146), [`ecb2a6b`](ecb2a6b), [`901eb27`](901eb27)]: - @browserbasehq/stagehand@4.3.0 - @browserbasehq/stagehand-integrations@4.0.6 ## @browserbasehq/stagehand-integrations-example-mastra-facade@4.0.6 ### Patch Changes - Updated dependencies []: - @browserbasehq/stagehand-integrations@4.0.6 ## @browserbasehq/stagehand-integrations-mastra-sdk@4.0.5 ### Patch Changes - Updated dependencies []: - @browserbasehq/stagehand-integrations@4.0.6 ## @browserbasehq/stagehand-integrations-example-pi-facade@4.0.6 ### Patch Changes - Updated dependencies [[`c6b6f27`](c6b6f27), [`cb9a1d1`](cb9a1d1), [`10418f9`](10418f9), [`1c40ba7`](1c40ba7), [`17de6ea`](17de6ea), [`6bf4146`](6bf4146), [`ecb2a6b`](ecb2a6b), [`901eb27`](901eb27)]: - @browserbasehq/stagehand@4.3.0 - @browserbasehq/stagehand-integrations@4.0.6 ## @browserbasehq/stagehand-integrations-pi-sdk@4.0.5 ### Patch Changes - Updated dependencies []: - @browserbasehq/stagehand-integrations@4.0.6 ## @browserbasehq/stagehand-integrations-example-vercel-ai-facade@4.0.6 ### Patch Changes - Updated dependencies []: - @browserbasehq/stagehand-integrations@4.0.6 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Stagehand's extension opens its own CDP connection to the browser, and it is handed the URL the Python client connected with. That only works when the client and the browser can use the same address.
Sometimes they can't. Our case: the client runs in a server process and reaches a sandboxed Chrome through an authenticated gateway,
wss://<gateway>/.../cdp?api_key=.... The extension, running inside that Chrome, should dial the browser's local endpoint (ws://127.0.0.1:<port>/devtools/browser/<id>). Using the gateway URL would hand the gateway's credentials to the browser, which loads arbitrary pages, and send every extension command out and back in through the gateway. Today the only workaround is overriding the privateStagehand._worker_init_params.What
local_browser.connecttakes an optionalextension_cdp_url, which must be aws://orwss://URL with a host.Stagehand.createsends it as the init parambrowser_cdp_urlin place of the client's URL.Tests
ruff format --check,ruff check,ty checkandgenerate.py --checkpass.pytest: 793 passed.test_official_api_routes_through_and_never_closes_a_caller_http_clientfails with and without this change in my checkout, because it needs the extension built inpackages/extension/dist.🤖 Generated with Claude Code
Summary by cubic
Adds an optional
extension_cdp_urltolocal_browser.connectin the Python SDK so Stagehand's extension can dial a different CDP WebSocket than the client's, when the client reaches Chrome through a proxy or gateway the extension cannot use.Stagehand.createsends it as the init parambrowser_cdp_urlin place of the client's URL; when unset, behavior is unchanged.ws://orwss://URL with a host; invalid schemes, ports (including 0), or missing hostnames raise a validation error.pytestreports 793 passed;test_official_api_routes_through_and_never_closes_a_caller_http_clientfails with and without this change because it needs the extension built inpackages/extension/dist.Written for commit 48a3789. Summary will update on new commits.