Skip to content

v3.6.0: sessions.start() with cdpUrl fails to connect to already-launched browser #316

@mohamedmamdouh22

Description

@mohamedmamdouh22

Description

After upgrading to v3.6.0, passing a cdpUrl in sessions.start() to connect to an already-launched browser no longer works reliably. The session starts but Stagehand fails to attach to the existing browser process.

Root Cause

The change in PR #308 (src/stagehand/_base_client.py) switched request body serialization from httpx's built-in json= parameter to raw content= bytes:

v3.5.0:

else:
    kwargs["json"] = json_data if is_given(json_data) else None

v3.6.0:

elif not files:
    kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None
kwargs["files"] = files

When httpx receives json=data, it automatically serializes the data and sets Content-Type: application/json. When content=bytes is used instead, httpx does not automatically set the Content-Type header. If the SDK relies on httpx to set this header (rather than setting it explicitly), the server receives the request body without a proper Content-Type: application/json, causing the launchOptions.cdpUrl field to be silently ignored or unparsed — and Stagehand never attaches to the existing browser.

Steps to Reproduce

from stagehand import AsyncStagehand

client = AsyncStagehand(server="local", model_api_key="...", local_ready_timeout_s=30.0)

session = await client.sessions.start(
    model_name="openai/gpt-4o",
    browser={
        "type": "local",
        "launchOptions": {
            "cdpUrl": "ws://127.0.0.1:9222/devtools/browser/<uuid>",
        },
    },
)
# Session starts but Stagehand launches a new browser instead of connecting to the existing one

Expected Behavior

Stagehand connects to the already-running browser at the provided cdpUrl.

Actual Behavior

Stagehand ignores the cdpUrl and either launches a new browser or fails to connect.

Workaround

Pin to v3.5.0 until this is resolved:

stagehand==3.5.0

Additional Notes

  • The change in _base_client.py was introduced to support executionModel serialization with camelCase aliases (by_alias=True) for pydantic models in the execute endpoint. The fix for that feature inadvertently changed behavior for all JSON requests.
  • A secondary regression was also flagged in the same PR: when files are provided alongside json_data, the JSON body is now silently dropped instead of raising a ValueError as httpx previously did.
  • The same issue exists in the TypeScript SDK.

Affected version: 3.6.0
Working version: 3.5.0

PS:
Facing the same issue in TS version 3.1.0 and working on version 3.0.8

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions