docs: add CONTRIBUTING.md for external contributors - #2561
docs: add CONTRIBUTING.md for external contributors#2561felipeofdev-ai wants to merge 1406 commits into
Conversation
# why
`act()` already supports variables, but `observe()` did not. That made
safety-sensitive flows like login automation harder to use correctly:
callers could inspect `observe()`
results before executing them, but then had to guess which returned
action should receive each secret value before calling `act()`.
This change brings variable support to `observe()` so it can return
placeholder-backed actions like `%username%` and `%password%`. That
preserves the existing safe pattern of:
1. `observe()` candidate actions
2. validate the returned actions
3. `act()` the validated actions with real variable values at execution
time
# what changed
- Added `variables?: Variables` support to `observe()` in the public SDK
types and internal handler params.
- Threaded `observe` variables through the local SDK path, inference
layer, and prompt builder.
- Updated the observe prompt so the model sees available variable names
and returns `%variableName%` placeholders in action arguments instead of
literal sensitive values.
- Added observe variable support to the hosted/API path, including
schema updates and flattening rich variable values to the existing wire
format.
- Updated the internal `fillForm` tool to pass variables into
`observe()` as well as `act()`.
- Added docs for `observe({ variables })` and the validate-then-act
login flow.
- Added a dedicated example at
`packages/core/examples/observe_variables_login.ts` showing
placeholder-based login planning with `observe()`, explicit validation,
and execution via
`act()`.
# test plan
- Ran targeted unit tests covering:
- public `ObserveOptions` type support
- observe variable forwarding into inference/prompting
- placeholder preservation in returned observe actions
- API client observe variable serialization
- `fillForm` forwarding variables to `observe()`
- Ran:
- `pnpm --filter @browserbasehq/stagehand exec vitest run --config
/tmp/stagehand-vitest-source.config.mjs
tests/unit/public-api/public-types.test.ts tests/unit/agent-execution-
model.test.ts tests/unit/timeout-handlers.test.ts
tests/unit/api-client-observe-variables.test.ts`
- Ran formatting checks on changed files with Prettier.
- Added integration coverage for observe request schemas in both v3 and
v4 server tests.
- Full repo typecheck/build is still blocked by unrelated pre-existing
issues in `packages/core/lib/v3/launch/browserbase.ts` and existing
server test environment/type-resolution
failures.
…#1879) # why the previous docs theme was sunset and changed # what changed # test plan
## Why The stainless sdks are dropping the final finished SSE event instead of yielding it. This is due to the fact that we were not using `event` fields (basically setting event types) as per the SSE spec. The fix is to emit explicit SSE `event:` names and match them in Stainless. But on the hosted API we cannot switch that on for everyone at once, because older clients still expect the old `data:`-only SSE framing. Thus, we will need to have branching logic in our hosted server: 1. Legacy (old stainless sdks, stagehand-js): continue to not return `event` field. 2. New Stainless SDKs on `>= 3.13.0`: use typed SSE framing with `event:` + `data:`. Once this and it's core counterpart PR are merged, then we will release another version of all stainless sdks - `3.13`, which will be the first typed-SSE release. ## What Changed - emit explicit SSE `event:` names from the local v3 streaming helper while keeping the JSON `data:` payload unchanged - switch Stainless streaming matching to explicit `event_type` handlers - update the documented stream shape, regenerated v3 OpenAPI, and a focused integration assertion ## Testing - `pnpm --dir /tmp/stagehand-local-sse.lSk5Av --filter @browserbasehq/stagehand-server-v3 run gen:openapi` - `pnpm --dir /tmp/stagehand-local-sse.lSk5Av exec prettier --check stainless.yml` - `pnpm --dir /tmp/stagehand-local-sse.lSk5Av --filter @browserbasehq/stagehand lint` - `pnpm --dir /tmp/stagehand-local-sse.lSk5Av --filter @browserbasehq/stagehand-server-v3 lint` A test run with the three different client types against the updated server: <img width="765" height="230" alt="Screenshot 2026-03-20 at 11 01 50 AM" src="https://github.com/user-attachments/assets/af97c8ec-d7f9-4ae6-95a0-16a3c2906934" /> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Emit explicit SSE event names for v3 streaming (`starting`, `connected`, `running`, `finished`, `error`) while keeping the JSON `data:` payload unchanged. Updates the streaming helper, Stainless config, OpenAPI docs, and tests to use and verify typed events. - New Features - Server now sends `event: <status>` with `data: { data, type, id }`. - Switched `stainless.yml` streaming matching to `event_type` (yields on starting/connected/running/finished; handles `error`). - Added integration test asserting event names match payload status; updated OpenAPI description and core type docs. - Dependencies - Added changeset to publish patch updates for `@browserbasehq/stagehand` and `@browserbasehq/stagehand-server-v3`. <sup>Written for commit 96cd037. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1858">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. -->
# why Custom headers for LLM requests are supported in Stagehand, canonical SDKs were not accounting for this until now. # what changed Added headers to ModelConfig # test plan <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adds an optional `headers` field to `ModelConfig` to send custom headers with every LLM request. Also includes a patch changeset for `@browserbasehq/stagehand`. - **New Features** - Add `headers?: Record<string, string>` to `ModelConfigObjectSchema` in `packages/core/lib/v3/types/public/api.ts`. - Update `packages/server-v3/openapi.v3.yaml` to document `headers` in `ModelConfig`. - Update `packages/server-v4/openapi.v4.yaml` to use selector-based result/output schemas for page actions (click, hover, scroll, drag-and-drop) and add `returnSelector` support. <sup>Written for commit f83bd8b. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1874">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. --> ---------
…se#1873) # why JSON schema parsing for schemas generated from Pydantic `.model_json_schema()` were dropping the nested references # what changed Before (from test script) with schema: ```python class RequiredInputType(StrEnum): USERNAME = "username" EMAIL = "email" PHONE = "phone" OTP = "otp" PASSWORD = "password" CARD_LAST_8 = "card_last_8" SOMETHING_ELSE = "something_else" class PageState(StrEnum): LOGIN_FORM = "login_form" ERROR = "error" AUTHENTICATED = "authenticated" UNKNOWN = "unknown" class PageAnalysis(BaseModel): page_state: PageState required_input: RequiredInputType | None = None contact_id_of_required_input: str | None = None error_message: str | None = None reasoning: str = "" ``` <img width="654" height="373" alt="Screenshot 2026-03-23 at 1 09 01 PM" src="https://github.com/user-attachments/assets/29a24eea-cfda-40a8-8e43-10f959c71ad2" /> After: <img width="650" height="310" alt="Screenshot 2026-03-23 at 1 06 17 PM" src="https://github.com/user-attachments/assets/9f62d1ad-04bb-41e4-9dbc-85d7b68c84aa" /> 1. Added resolveRefs() function that inlines $ref/$defs before Zod conversion (Pydantic v2's model_json_schema() generates these) 2. Changed z.string().refine() → z.enum() so enum constraints are expressed in the schema sent to the LLM, not just validated post-hoc # test plan <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Fixes missing nested `$ref` when converting Pydantic v2 `.model_json_schema()` to Zod in `@browserbasehq/stagehand-server-v3`, and emits proper `z.enum()` types so LLM-facing schemas are accurate. Adds a `test:unit` script, moves tests to `tests/`, and updates the runner to scan directories or single files. - **Bug Fixes** - Resolves `$ref` from `$defs` before conversion via `resolveRefs()` with a cycle guard, handling refs in objects, arrays, `anyOf`/`oneOf`/`allOf`, and root-level refs (unknown refs fall back safely). - Replaces `z.string().refine()` with `z.enum()` so enum values are surfaced in generated schemas and enforced. <sup>Written for commit ae9e2e5. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1873">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. -->
…se#1890) ## Summary - Adds `userMetadata: { "browse-cli": "true" }` to every Browserbase session created through the browse CLI - Enables usage attribution to distinguish CLI-originated sessions from SDK-originated ones ## Test plan - [x] Run `browse open https://example.com` in Browserbase mode and verify the session has `browse-cli: true` in its metadata - [x] Verify local mode is unaffected 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adds `userMetadata: { "browse-cli": "true" }` to `browserbaseSessionCreateParams` for sessions started by `@browserbasehq/browse-cli` in Browserbase mode, enabling usage attribution and distinguishing CLI vs SDK sessions. Satisfies STG-1678; local mode is unaffected. <sup>Written for commit 58c10fd. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1890">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. -->
…rowserbase#1887) ## Summary - When the browse CLI daemon outlives its Chrome browser (e.g., between Claude Code sessions), `ensureBrowserInitialized()` returns the cached `stagehand`/`context` without checking if the browser is still alive - This causes `"No Page found for awaitActivePage: no page available"` errors requiring a manual `browse stop` + retry - Fix: register an `onTransportClosed` handler on the CDP connection that clears the cached state, so the next command triggers a full re-initialization ## Root cause The daemon is spawned as a detached process (`detached: true`, `child.unref()`) and persists across CLI sessions. When Chrome dies, `_onCdpClosed` fires and calls `stagehand.close()`, but the daemon's closure variables `stagehand` and `context` are never cleared. The next command hits the early return at `if (stagehand && context) { return ... }` and tries to use dead objects. ## Test plan **Regression test added to PR is illustrative** - [x] Start a `browse` session, close Chrome manually, run another `browse open` — should auto-recover instead of erroring - [x] Normal `browse` workflow (open, snapshot, click, stop) still works - [x] Daemon restart across mode switches (local ↔ remote) still works - [x] Regression test added: kills Chrome under daemon, verifies auto-recovery (fails on main, passes with fix) ## Verified test results **On `main` (without fix):** Test fails — after killing Chrome, the retry returns exit code 1 with `"Error: No Page found for awaitActivePage: no page available"`. The daemon is alive but returns stale cached Stagehand/context objects. **On this branch (with fix):** Test passes — after killing Chrome, the `onTransportClosed` handler nulls the cached state, and the retry triggers a full browser re-initialization. All 37 CLI tests pass (full suite, 67s). > **Note:** When building locally, use `npx tsup src/index.ts --format cjs --out-dir dist --no-splitting` inside `packages/cli/` rather than `pnpm run build:cli`, since turbo may serve a stale cached build that doesn't include the fix. 🤖 Generated with [Claude Code](https://claude.com/claude-code) ---------
…essions (browserbase#1889) ## Summary - Adds a global `--connect <session-id>` flag to the `browse` CLI that connects to an existing Browserbase session instead of creating a new one - Uses Stagehand core's existing `browserbaseSessionID` support with `keepAlive: true` so the session stays alive after the CLI disconnects - Follows the same state-file pattern as `--context-id` (writes session ID to `/tmp/browse-{session}.connect`, daemon reads it during initialization) ## Test procedure ``` # 1. Sync cookies node cookie-sync.mjs --domains github.com # → Session ID: 3ee737d4-237f-4557-a1ca-09a3d5dcbaf2 # 2. Connect and browse (dev build) browse --connect 3ee737d4-... open https://github.com/notifications ✅ browse eval "..." ✅ ``` ## Tests completed - [x] `browse --connect <id> open ...` in local mode → errors with "only supported in remote mode" - [x] `browse --connect <id> open --context-id <ctx> ...` → errors with mutual exclusion message - [x] `--connect` writes session ID to connect file before daemon start - [x] Commands without `--connect` clear stale connect file - [x] `browse status` output includes `browserbaseSessionId` field - [x] Manual: `browse --connect <valid-bb-session> open https://example.com` connects and navigates - [x] Manual: `browse stop` disconnects without killing the BB session 🤖 Generated with [Claude Code](https://claude.com/claude-code) Fixes STG-1672 <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adds a `--connect <session-id>` flag to the `browse` CLI to attach to an existing Browserbase session instead of creating a new one, leaving the session running after the CLI exits. Implements STG-1672. - **New Features** - New global `--connect <session-id>` for `browse` to attach to an existing Browserbase session - Remote mode only; errors in local mode - Mutually exclusive with `--context-id` - Persists the session ID to `/tmp/browse-{session}.connect`; clears when unused and restarts the daemon if the ID changes - Uses `browserbaseSessionID` with `keepAlive: true` so the session persists after disconnect - `browse status` now includes a `browserbaseSessionId` field - Minor release for `@browserbasehq/browse-cli` <sup>Written for commit aff1bad. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1889">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. --> ---------
…base#1885) ## Summary - Adds `@browserbasehq/browse-cli` to the changeset `ignore` list so the main "Version Packages" PR no longer includes CLI version bumps - Creates a new `release-cli.yml` workflow (triggered via `workflow_dispatch`) that independently versions and publishes browse-cli to npm ### How it works **Main release flow (`release.yml`)** — unchanged, but now skips browse-cli. Core stagehand changesets are versioned and published as before. **CLI release flow (`release-cli.yml`)** — triggered manually via GitHub Actions "Run workflow": 1. Detects pending browse-cli changesets (fails early if a changeset mixes browse-cli with other packages) 2. Temporarily swaps the changeset ignore config to version only browse-cli 3. Runs `changeset version` to consume CLI changesets and bump `packages/cli/package.json` 4. Builds all packages, publishes browse-cli to npm via Trusted Publishing 5. Commits the version bump and pushes a git tag ### Constraints - Changesets that reference **both** browse-cli and another package (e.g., stagehand core) must be split into separate changeset files — the workflow enforces this with a pre-check - browse-cli is excluded from canary (`alpha`) releases — add a canary step to `release-cli.yml` if needed later ## Test plan - [ ] Verify existing `release.yml` still creates "Version Packages" PR for core changesets (browse-cli changesets should be excluded) - [ ] Trigger `release-cli.yml` via workflow_dispatch with a pending browse-cli changeset and verify it publishes correctly - [ ] Verify mixed changeset detection fails the workflow with a clear error 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Decouples the `@browserbasehq/browse-cli` release cycle from stagehand core so we can ship CLI updates independently. Implements STG-1663. - **New Features** - Adds `@browserbasehq/browse-cli` to `.changeset` ignore so `release.yml` skips CLI. - Adds `release-cli.yml` (manual via `workflow_dispatch`, restricted to `main`) to version and publish CLI with Trusted Publishing. - Pre-checks for CLI-only changesets; fails on mixed changesets. - Temporarily swaps `.changeset` ignore to version only CLI and strips the `@browserbasehq/stagehand` workspace dep before `changeset version` to satisfy Changesets; restores both after. - Builds and publishes `packages/cli` to npm, commits the bump, and tags. - **Migration** - Split changesets that touch CLI and core. - Use the “Release CLI” workflow to publish CLI releases. - Canary releases for CLI are not included. <sup>Written for commit c298904. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1885">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. --> ---------
…e-cli workflow (browserbase#1893) ## Summary - The `pnpm changeset version` step in `release-cli.yml` calls `@changesets/changelog-github`, which fetches PR metadata from the GitHub API and requires `GITHUB_TOKEN` to authenticate - The token was only passed to the "Commit version bump and tag" step, so the version step failed with a 403 when trying to generate changelogs - Added `env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}` to the "Version browse-cli" step **Failed run:** https://github.com/browserbase/stagehand/actions/runs/23624352586/job/68810210512 ## Test plan - [ ] Trigger the `Release CLI` workflow on main after merging and verify the version step succeeds 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Pass `GITHUB_TOKEN` to the changeset version step in the Release CLI workflow to authenticate `@changesets/changelog-github` and prevent 403s during changelog generation. Addresses STG-xxx. - **Bug Fixes** - Added `env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}` to the "Version browse-cli" step so `pnpm changeset version` can fetch PR metadata. <sup>Written for commit 2f49dfb. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1893">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. -->
# why - OpenAI now requires a screenshot on initial message for computer use agents # what changed - added screenshot to initial message - imported types from openai sdk for stronger typing # test plan - tested locally using operator-example script <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Send an initial screenshot with the first message in `OpenAICUAClient` to meet OpenAI’s computer-use requirement and prevent startup errors. Also adopt `openai` SDK response types for stricter typing. - **Bug Fixes** - Capture and attach a high-detail screenshot on the first request when available; gracefully skip on failure. - Build the first message using `EasyInputMessage`, `ResponseInputText`, and `ResponseInputImage`; include an optional system message from `userProvidedInstructions`; make `createInitialInputItems` async and update method signatures to accept a unified `OpenAIRequestInputItem` type. - **Dependencies** - Add changeset for a patch release of `@browserbasehq/stagehand`. <sup>Written for commit f01d8a8. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1899">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. -->
# why - to group two types of clicking into a single route. ie, we don't need to add a separate `/sendClickEvent` route # what changed - added an optional `synthetic` parameter to the `/click` schema, which we will internally route to `.sendClickEvent()` ### screenshot: <img width="436" height="693" alt="Screenshot 2026-03-24 at 3 58 50 PM" src="https://github.com/user-attachments/assets/75a35a09-3f92-4b25-bfcb-8dc36a328088" /> # test plan - added a test to verify passing `synthetic: true` passes validation <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Add a `method` param to the v4 `/click` route to support both coordinate-based and JS event clicks in one endpoint, defaulting to `xy` for backward compatibility. Caps `clickCount` at 3 and updates the OpenAPI/action output. Addresses Linear STG-1659. - **New Features** - Adds `method: 'xy' | 'jsevent'` (default `xy`) to `/click` params; action output now includes and requires `method` via `PageClickParamsOutput`. - Limits `clickCount` to 1–3 in schema and OpenAPI in `packages/server-v4`. - Adds integration tests for coordinate clicks and `method: "jsevent"` selector clicks. <sup>Written for commit c02c521. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1884">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. --> ---------
Adds the v4 `/llms` stubs, and the internal zod entities we will map to
drizzle DB objects later.
Note: An internal `LLMSession` is not the full accumulated history of an
entire browser session. It is the container for the LLM thread involved
in a single logical Stagehand operation, which today is usually very
small. For example, an `act()` call may create one `LLMSession` with one
`LLMCall`; `extract()` may create one `LLMSession` with two `LLMCall`
rows. It is **not** a giant transcript of every LLM call across the
whole browser session.
### Public:
- `/v4/llms` for reusable LLM configs
- `BrowserSession.llmId` as the primary/default LLM config reference,
plus `actLlmId`, `observeLlmId`, and `extractLlmId` as public
per-operation override references
### Internal only (for now):
- `LLMSession`
- `LLMCall`
- `StagehandStep`
- the browser-session-to-llm-session/call relationships shown in the
diagram below
We are intentionally not exposing `LLMSession` / `LLMCall` history in
this PR. That data can still be surfaced through logs/streaming work,
and we can decide later whether we want a first-class public API for it.
### Sequence Example
1. User creates a browser session.
2. If they do not pass `llmId`, the server materializes a real default
LLM config with `source = "system-default"` and attaches it to the
session.
3. If they want custom behavior up front, they can `POST /v4/llms` first
and then create the browser session with `llmId`.
4. If they want different defaults for specific operations, they can set
`actLlmId`, `observeLlmId`, and/or `extractLlmId` on the browser
session.
5. When an `act` / `observe` / `extract` call runs, Stagehand resolves
the template config from the per-operation override or falls back to
`llmId`, creates a dedicated `LLMSession` for that operation, and
records one or more `LLMCall` rows under it.
6. Updating an LLM config changes that reusable config resource;
updating a browser session changes which config ids future steps resolve
from.
```mermaid
classDiagram
class BrowserSession {
id: uuid
projectId: uuid
env: BrowserSessionEnv
status: BrowserSessionStatus
browserbaseSessionId: uuid?
cdpUrl: string?
llmId: uuid
actLlmId: uuid?
observeLlmId: uuid?
extractLlmId: uuid?
createdAt: datetime
updatedAt: datetime
endedAt: datetime?
}
class LLMSession {
id: uuid
copiedTemplateId: uuid?
forkedSessionId: uuid?
projectId: uuid
browserSessionId: uuid
createdAt: datetime
updatedAt: datetime
connectedAt: datetime?
disconnectedAt: datetime?
lastRequestAt: datetime?
lastResponseAt: datetime?
lastErrorAt: datetime?
lastErrorMessage: string?
status: LLMSessionStatus
model: string
baseUrl: string?
options: json?
extraHttpHeaders: json?
systemPrompt: string?
tokensInput: int
tokensOutput: int
tokensReasoning: int
tokensCachedInput: int
tokensTotal: int
}
class LLMCall {
id: uuid
llmSessionId: uuid
sentAt: datetime
receivedAt: datetime?
prompt: string
expectedResponseSchema: json?
response: json?
error: json?
usage: json?
model: string
}
class StagehandStep {
id: uuid
stagehandBrowserSessionId: uuid
operation: StagehandStepOperation
llmTemplateId: uuid
llmSessionId: uuid?
params: json
result: json?
}
class LLMConfig {
id: uuid
projectId: uuid
source: LLMSource
displayName: string?
modelName: string
baseUrl: string?
systemPrompt: string?
providerOptions: LLMProviderOptions?
createdAt: datetime
updatedAt: datetime
}
BrowserSession "1" <-- "*" LLMSession : has many
LLMSession "1" --> "*" LLMCall : has many
BrowserSession "1" --> "*" StagehandStep : has many
StagehandStep "1" --> "0..1" LLMSession : resolves to
LLMConfig "1" <-- "*" BrowserSession : is referenced by many
```
Implementation notes
- Auto-created default configs are real `/llms` resources and appear in
`GET /v4/llms`.
- Defaults should be treated as versioned server presets in
implementation, not just implicit column defaults.
- Persisted public `LLM` configs remain non-secret; request auth still
stays out of the public config resource.
- Internally, `LLMSession` is the config-bearing per-operation thread
and `LLMCall` is the append-friendly log of provider exchanges under
that thread.
# why
- the existing /page/scroll endpoint had an awkward union of
`PageScrollElementParams` (element selector + percentage) and
`PageScrollCoordinateParams` (coordinate selector + deltaX/deltaY).
- these two modes were hard to distinguish and didn't cover common
scrolling patterns like "scroll N pages" or "scroll until this element
is visible"
# what changed
- replaced the two old scroll param schemas with a cleaner 3-way
discriminated union:
- `PageScrollByOffsetParams`: scroll by pixel delta (`{ cursorPosition?,
offset: {x, y} }`)
- `PageScrollByPagesParams`: scroll by viewport heights (`{
cursorPosition?, pages: number, delayBetweenMs? }`)
- `PageScrollToTargetParams`: scroll until a target is visible (`{
target: Selector }`)
- `cursorPosition` is an optional `Selector` that determines where the
mouse cursor is placed before scrolling. this controls which scrollable
container receives the event.
# test plan
- updated the route stub and integration test to match the new schema
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Refactors the v4 `/page/scroll` route into a simple 3‑mode API and
updates OpenAPI to return a typed `PageScrollActionOutput`. The stub now
returns `{x: 0, y: 0}`; adds `position` for target scrolls and
`delayBetweenMs` for page scrolling, aligning with Linear STG-1644.
- **Refactors**
- Replaced old params with 3 modes: by offset `{offset:{x,y}}`, by pages
`{pages, delayBetweenMs?}`, or to target `{target, position?}`; optional
`{cursorPosition}` selects the scroll container.
- OpenAPI now uses `PageScrollActionOutput` and
`PageScroll*ParamsOutput` (defaults resolved, stricter requireds) and
wires it into the `PageAction` union.
- Route stub execution simplified to always return `{x: 0, y: 0}`.
- **Migration**
- Stop using `selector + percentage` and `selector + deltaX/deltaY`.
- Send one of: `{offset:{x,y}}`, `{pages, delayBetweenMs?,
cursorPosition?}`, or `{target, position?}`.
- Expect `{x,y}` in the result; the action shape is now
`PageScrollActionOutput`.
<sup>Written for commit b8a85a3.
Summary will update on new commits. <a
href="https://cubic.dev/pr/browserbase/stagehand/pull/1875">Review in
cubic</a></sup>
<!-- End of auto-generated description by cubic. -->
# why - to add a single `/elementInfo` route which encapsulates multiple `locator` functions # what changed - added an `/elementInfo` route, which accepts a `Selector`, and returns the following info: - `count` - `isVisible` - `isChecked` - `inputValue` - `textContent` - `innerHTML` - `innerText` - `centroid` ### screenshot: <img width="538" height="693" alt="Screenshot 2026-03-23 at 5 10 55 PM" src="https://github.com/user-attachments/assets/75d7cab4-5f47-40fc-bebb-8b0fb6a8b192" /> # test plan - added a test which verifies that the `/elementInfo` route accepts a request <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adds the v4 `page.elementInfo` endpoint (`POST /v4/page/elementInfo`) to fetch common element details in one call. The handler is a stub that returns defaulted fields to unblock client work. - **New Features** - Implemented `page.elementInfo` route that accepts a `Selector` and optional `fields` filter; returns: `count`, `selector`, `tagName`, `backendNodeId`, `visibility`, `domRects`, `content`, and optional `inputInfo`, `ariaInfo`, `attributes`, `styles`. - Wired the route into v4 page routes, added request/action/result/response schemas to OpenAPI, and added an integration test posting an XPath selector to assert a successful action. <sup>Written for commit ebdb216. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1877">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. -->
# why - to add page routes for more of the locator functions # what changed - added the following route stubs: - `/fill` - `/highlight` - `/selectOption` - `/setInputFiles` - each of the above optionally returns a selector object if `returnSelector: true` - aligned existing `/click`, `/hover`, and `/dragAndDrop` routes to default `returnSelector` to `false` ### screenshot: <img width="606" height="925" alt="Screenshot 2026-03-24 at 1 25 11 PM" src="https://github.com/user-attachments/assets/a7ec4602-2ad1-44e9-81be-54208021bc43" /> # test plan <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adds v4 page route stubs for locator actions: /v4/page/fill, /v4/page/highlight, /v4/page/selectOption, and /v4/page/setInputFiles. Addresses STG-1646 with OpenAPI and schema updates; routes return placeholder results with optional selector output. - **New Features** - Added POST endpoints: /v4/page/fill, /v4/page/highlight, /v4/page/selectOption, /v4/page/setInputFiles (wired into router and documented). Each supports returnSelector (default false) and returns placeholder results. - **Refactors** - Default returnSelector is now false for /click, /hover, and /dragAndDrop. - ResultSelector is now required in result schemas. <sup>Written for commit 3637fdb. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1882">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. -->
…owserbase#1886) ## Summary - `browse env local` now auto-discovers an already-running Chrome with remote debugging enabled (via `DevToolsActivePort` files and common port probing), attaching to it instead of always launching an isolated browser - Falls back to isolated launch when no debuggable Chrome is found — keeps the command reliable - Added `--isolated` flag to force clean isolated browser (old behavior) - Added positional CDP target argument: `browse env local 9222` or `browse env local ws://...` - `browse status` now reports `localStrategy`, `localSource`, `resolvedCdpUrl`, and `fallbackReason` **Sister PR:** browserbase/skills#54 ## Test plan <img width="698" height="373" alt="image" src="https://github.com/user-attachments/assets/1612cf7e-61f4-46b8-9cc6-e739c7363a5a" /> - [x] All 44 existing tests pass - [x] `browse env local` with Chrome running + `--remote-debugging-port=9222` → attaches to existing Chrome - [x] `browse env local` without debuggable Chrome → falls back to isolated launch - [x] `browse env local --isolated` → always launches clean browser - [x] `browse env local 9222` → persists CDP strategy targeting 127.0.0.1:9222 - [x] `browse status` shows local strategy details - [x] `browse env remote` → unchanged 🤖 Generated with [Claude Code](https://claude.com/claude-code) ---------
# why - the `timeoutMs` param in `goto` was not being respected when using the stagehand api - the `/navigate` route on the server expects `timeout`, while the actual `goto()` implementation in node expects `timeoutMs` # what changed - added logic to correctly send the `timeoutMs` param to the server as `timeout` - added server side logic to map the `timeout` back to `timoeutMs` (which is what `goto()` expects) ### screenshot: (navigate correctly adheres to defined timeout) <img width="691" height="462" alt="Screenshot 2026-03-27 at 4 13 04 PM" src="https://github.com/user-attachments/assets/946de567-70d5-4fa9-be8e-646bb996e894" /> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Fixes timeout handling for navigation by mapping `timeoutMs` on the client to `timeout` over the API, then back to `timeoutMs` for `page.goto()`. Navigations now respect the configured timeout when using the Stagehand API. - **Bug Fixes** - Client sends `timeout` to `/navigate` from `options.timeoutMs`. - Server maps `timeout` to `timeoutMs` before calling `page.goto()`. <sup>Written for commit 783fa2b. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1901">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. -->
…browserbase#1888) ## Summary - Adds a pre-commit hook that runs prettier on staged files to catch formatting issues before CI - Uses `husky` for git hooks and `lint-staged` to scope prettier to only staged files - Preserves the existing `prepare` script behavior (`node packages/core/scripts/prepare.js`) ## Changes - Added `husky` and `lint-staged` as root devDependencies - Created `.husky/pre-commit` hook that runs `pnpm exec lint-staged` - Configured lint-staged: `{ "*": "prettier --write --ignore-unknown" }` (respects `.prettierignore`) ## Test plan - [x] Hook runs on commit — verified: badly formatted file auto-fixed during commit - [x] `pnpm install` sets up hooks correctly via `prepare` script 🤖 Generated with [Claude Code](https://claude.com/claude-code) ---------
## Why The original server-v4 stacked PRs merged into each other after `STG-1614` had already merged into `main`, so the env/test-harness work, Drizzle foundation, and Drizzle schema/controller flow never actually reached `main`. This PR recovers that full stack onto current `main` in one branch. ## What Changed - cherry-picked the three stacked squash-merge commits from `browserbase#1896`, `browserbase#1897`, and `browserbase#1898` onto fresh `main` - preserved the full `server-v4` env/test harness cleanup, Drizzle foundation, DB smoke tests, schema/migration work, and `/v4/llms` controller/service/repo flow - added one small follow-up commit to normalize `pnpm-lock.yaml` against current `main` after applying the recovered stack ## Test Plan - `pnpm install` - `pnpm --filter @browserbasehq/stagehand-server-v4 run lint` - `pnpm --filter @browserbasehq/stagehand-server-v4 run test:unit` - `pnpm --filter @browserbasehq/stagehand-server-v4 run build` - `pnpm --filter @browserbasehq/stagehand-server-v4 run gen:openapi` <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Recovers the server‑v4 Drizzle stack onto `main`, restoring DB‑backed `/v4/llms` and switching the public `LLMId` to UUID. Adds a typed env, clean app bootstrap, and a local `pglite`/Postgres runtime with migrations, tests, and a simpler source‑based test harness. - **New Features** - Drizzle foundation with `pglite` default and Postgres option; new `db:*` scripts via `drizzle-kit`. - Fastify `databasePlugin` and unified DB client; `app.ts` bootstrap with health/readiness and Swagger/OpenAPI; env via `@t3-oss/env-core`. - Tables/relations for LLMs and Stagehand; DB‑derived Zod schemas; first migration; OpenAPI updated for UUID `LLMId` with strict format and pattern. - LLM module (repo/service/controller) backing `/v4/llms`; routes now delegate to the controller; browser session create uses the service and auto‑creates a system default LLM when `llmId` is missing. - Tests run from source via `tsx`; test server script simplified; `turbo` tasks cleaned up; schema cleanup (`z.url()`). - **Migration** - Run: `pnpm --filter @browserbasehq/stagehand-server-v4 db:migrate`. - Local dev uses `pglite` at `~/.stagehand/db/stagehand-v4`. For Postgres set `STAGEHAND_DB_MODE=postgres` and `DATABASE_URL`. - Update clients to treat `LLMId` as a UUID and regenerate from `openapi.v4.yaml` if needed. <sup>Written for commit 1fb861e. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1917">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. --> ---------
## Summary Move browse-cli to a merge-first release flow so `main` becomes the source of truth before npm is mutated. ## New release flow 1. Merge normal feature PRs into `main`, with browse-cli changesets as usual. 2. Trigger `Prepare CLI Release` when browse-cli is ready to ship. 3. That workflow versions only `@browserbasehq/browse-cli` and opens or updates a release PR. 4. Merge the release PR. 5. The existing `Release` workflow on `main` detects the browse-cli version bump, publishes a packed tarball to npm with provenance, and tags the merge commit. ## What changed - rewired `release-cli.yml` into a prepare-only workflow that creates or updates a release PR instead of publishing directly - moved browse-cli publishing into `release.yml`, gated on an actual version bump landing on `main` - kept the `pnpm pack` -> `npm publish --provenance` path so workspace dependencies are resolved in the published tarball - made the main-branch publish path idempotent when that browse-cli version is already on npm - kept browse-cli tagging on the merge commit instead of trying to tag from the manual workflow - synced `packages/cli/package.json` to `0.3.0` and documented npm `0.3.0` as a broken publish in the CLI changelog ## Why this is better The previous standalone release design could succeed at publishing to npm and then fail while trying to sync `main`, which left npm ahead of git. That is the hard failure mode because npm versions are immutable. This PR flips the order: - git changes land first through a normal PR - npm publish happens afterward from the exact merged commit That removes the whole class of "published version is not reflected on `main`" failures and makes reruns much safer. ## Validation - `pnpm exec prettier --check .github/workflows/release.yml .github/workflows/release-cli.yml packages/cli/package.json packages/cli/CHANGELOG.md` - packed `packages/cli` locally and verified the tarball manifest resolves `@browserbasehq/stagehand` to `3.2.0` - confirmed the currently published npm `0.3.0` manifest still contains the broken `workspace:*` dependency - simulated the prepare flow in a throwaway worktree and confirmed it would cut `0.4.0` from the current 2 minor + 2 patch browse-cli changesets
Prepare the next browse-cli release by versioning the package on `main`. What this PR does: - bumps `packages/cli/package.json` to `0.4.0` - updates the browse-cli changelog - consumes the pending browse-cli changesets After this PR merges, the `Release` workflow on `main` will publish `@browserbasehq/browse-cli@0.4.0` from that exact commit using `pnpm pack` + `npm publish --provenance`. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Release `@browserbasehq/browse-cli` 0.4.0 with new session attach and smarter local Chrome discovery, plus stability fixes. Bumps the package version and updates the changelog; the Release workflow will publish from this commit. - **New Features** - Add `--connect` to attach to an existing Browserbase session by ID. - `browse env local` auto-discovers debuggable Chrome and attaches when found (falls back to isolated). Adds `--isolated`, positional CDP target, and `--ws` accepts bare port numbers. - **Bug Fixes** - Add CLI metadata to Browserbase sessions created via the CLI. - Clear cached browser state when CDP connection drops to prevent “awaitActivePage: no page available” errors. <sup>Written for commit d744934. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1925">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. -->
# why - models would sometimes respond with the variable name without wrapping it in percentage signs. - when this happens, the regex would fail to replace the variable name with the actual variable value # what changed - adjusted the prompting for the condition when `variables` are provided by the user - the updated prompting is more specific around what the variables are used for, and the required return shape. # test plan - existing act evals <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Hardened the act variables prompt so models always return argument placeholders as %variableName%, preventing failed variable substitution. Also clarified the element ID format with concrete examples. - **Bug Fixes** - Added explicit, repeated guidance to use %variableName% in the `arguments` array when variables are provided. - Improved element ID schema description with examples like '0-76' and '16-21'. - **Refactors** - Introduced `buildActVariablesPrompt()` and reused it in both act and step-two prompts to keep messaging consistent and avoid duplication. <sup>Written for commit 8b592f2. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1922">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. -->
# why - when a user created a new tab by clicking the "+" icon in a headful chromium browser, stagehand would not attach to it, because it filtered out the `"chrome://newtab"` url pattern # what changed - extended the conditional in `isNonWebTarget()` to allow for `"chrome://newtab"` URLs to pass through, so that stagehand can attach to them <img width="491" height="95" alt="Screenshot 2026-03-30 at 4 00 48 PM" src="https://github.com/user-attachments/assets/64638fc4-7334-4438-8df5-5a91344b6afb" /> # test plan - added a test which creates a newtab target, & confirms that a new page object gets created <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Fixes `@browserbasehq/stagehand` not attaching to tabs opened via the "+" button in Chromium by tracking `chrome://newtab/` page targets so a page is created and becomes usable after navigation. - **Bug Fixes** - Updated `isNonWebTarget()` to always track top-level `page` targets, even with non-web schemes like `chrome://newtab/`. - Added Playwright tests to verify `chrome://newtab/` tabs appear in `pages()` and work after navigating to a web URL. <sup>Written for commit 9791271. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1924">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. -->
…erbase#1911) ## Summary - The `browse-cli` metadata key (with hyphen) is rejected by the Browserbase API as an invalid metadata key, causing `env remote` to fail with a `400 Key is not a valid metadata key: browse-cli` error. - Changed the key to `browse_cli` (underscore) which is a valid metadata key format. Linear: https://linear.app/browserbase/issue/STG-1733/fix-use-valid-metadata-key-for-browse-cli-sessions ## Test plan - [x] Verified old key (`browse-cli`) returns 400 from Browserbase API - [x] Verified new key (`browse_cli`) returns 201 and session creates successfully 🤖 Generated with [Claude Code](https://claude.com/claude-code) ---------
Prepare the next browse-cli release by versioning the package on `main`. What this PR does: - bumps `packages/cli/package.json` to `0.4.1` - updates the browse-cli changelog - consumes the pending browse-cli changesets After this PR merges, the `Release` workflow on `main` will publish `@browserbasehq/browse-cli@0.4.1` from that exact commit using `pnpm pack` + `npm publish --provenance`. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Release `@browserbasehq/browse-cli@0.4.1`, a patch that fixes an invalid session metadata key. This bumps the package version and updates the changelog for publishing from `main`. - **Bug Fixes** - Use underscore in `browse_cli` session metadata keys instead of a hyphen. <sup>Written for commit 022c400. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1932">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. -->
…test (browserbase#1934) ## Summary - **Root cause:** The `changesets/action` step corrupts git state by checking out `changeset-release/main` and creating a new commit. After it returns, `HEAD` points to the wrong branch/commit. The browse-cli check step uses `HEAD`/`HEAD^` which are now wrong, so it always says "browse-cli version did not change" and skips the publish. Then the stagehand canary step (`changeset publish --tag alpha`) finds the unpublished version and publishes it under `alpha` instead of `latest`. - **Fix:** Replace all `HEAD`/`HEAD^` references with `${{ github.sha }}`/`${{ github.sha }}^` (the actual merge commit SHA, immune to branch switches). Add `git checkout ${{ github.sha }}` to restore the working tree. Add explicit `--tag latest` to the publish command. - **New:** Add a browse-cli canary step that publishes `X.Y.Z-alpha-{sha}` versions under the `alpha` tag on feature branch merges (matching stagehand's existing canary pattern). ## What was happening 1. PR merged to main with browse-cli version bump (e.g. 0.3.0 -> 0.4.0) 2. `changesets/action` runs, switches to `changeset-release/main`, creates commit 3. `HEAD` now points to the changeset PR commit, not the merge commit 4. browse-cli check compares wrong commits, finds no version change, sets `should_publish=false` 5. Stagehand canary step runs `changeset publish --tag alpha`, which picks up the unpublished browse-cli and publishes it under `alpha` 6. Result: `npm info @browserbasehq/browse-cli` shows `latest: 0.3.0`, `alpha: 0.4.0` ## What this PR does 1. Uses `${{ github.sha }}` (the actual merge commit) instead of `HEAD` for all git comparisons 2. Adds `git checkout ${{ github.sha }}` to restore the working tree after changesets/action 3. Adds `--tag latest` to the `npm publish` command for clean releases 4. Adds a new "Publish browse-cli canary" step for feature branch merges (publishes `X.Y.Z-alpha-{sha}` under `alpha` tag) ## Validation - Traced CI logs from recent releases to confirm `HEAD` was pointing to `changeset-release/main` after the changesets step - Confirmed `npm view @browserbasehq/browse-cli` shows `latest: 0.3.0` while `alpha: 0.4.1` (the bug in action) - `${{ github.sha }}` is set by GitHub Actions to the merge commit SHA and is immutable throughout the run ## Linear https://linear.app/browserbase/issue/STG-1739/fix-browse-cli-release-publishes-under-alpha-instead-of-latest 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Fixes the release workflow so `@browserbasehq/browse-cli` clean releases publish to `latest`, and canaries publish to `alpha`. Addresses Linear STG-1739 by using the immutable merge SHA to avoid `changesets/action` HEAD switches. - **Bug Fixes** - Use `${{ github.sha }}`/`${{ github.sha }}^` for diffs and version reads to avoid wrong `HEAD`. - Restore the working tree before checks/publish and reset `packages/cli/package.json` after canary publish. - Publish clean releases with `npm publish ... --tag latest`. - **New Features** - Add canary step that publishes `X.Y.Z-alpha-{shortSha}` to `alpha` when CLI files changed but no clean release occurred. - Skip canary if the version already exists or no CLI changes are detected. <sup>Written for commit c075ece. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1934">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. --> ---------
# why
- In some scenarios, users need more granular control / access to the
LLM at the provider leve
# what changed
- Added support for passing custom "middleware" in the model options
# test plan
- wrote tests
- tested locally with own middleware
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds first-class LLM middleware to intercept and wrap model calls for
logging, usage tracking, or request transforms without changing call
sites. Per-call middleware is used only for that call and is not cached.
- **New Features**
- Accepts `middleware: LanguageModelV2Middleware` in `V3` model config;
`LLMProvider` and `getAISDKLanguageModel` wrap models via
`wrapLanguageModel` from `ai`.
- Supports per-call overrides via `model` options; override middleware
is passed through `LLMProvider.getClient`, applies only to that call,
and is never cached.
- Works across providers and preserves `modelId`, `doGenerate`, and
`doStream`. Tests cover usage capture, streaming, errors, chaining, and
edge cases.
- **Migration**
- To use: pass `middleware` in `model` config or a per-call override,
e.g. `{ modelName: 'openai/gpt-4o', apiKey: '...', middleware }`.
- Middleware is only applied in direct/local execution and is not
serialized over HTTP.
<sup>Written for commit b09e42c.
Summary will update on new commits. <a
href="https://cubic.dev/pr/browserbase/stagehand/pull/1872">Review in
cubic</a></sup>
<!-- End of auto-generated description by cubic. -->
… family (browserbase#1852) Mirrored from external contributor PR browserbase#1844 after approval by @miguelg719. Original author: @praveentcom Original PR: browserbase#1844 Approved source head SHA: `a637dc329bfc5426bb71c8551c812191ed631527` @praveentcom, please continue any follow-up discussion on this mirrored PR. When the external PR gets new commits, this same internal PR will be marked stale until the latest external commit is approved and refreshed here. ## Original description All GPT-5.x series models don't support `minimal` as the `reasoningEffort`. Currently, it is enabled only for GPT-5.1 and GPT-5.2 models to set the reasoningEffort as `low`. This would start throwing errors like these. `Unsupported value: 'minimal' is not supported with the 'gpt-5.4' model. Supported values are: 'none', 'low', 'medium', 'high', and 'xhigh'.` This PR fixes the behavior to set the reasoning effort as low for all GPT-5.x series models so that we don't need to manually patch it every time when a new SOTA model is released. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Default `reasoningEffort` to "none" for all `gpt-5.*` models (excluding `codex`) to avoid unsupported "minimal" errors, with an override via `ClientOptions.reasoningEffort`, and default GPT-5 `textVerbosity` set to "low". Also plumbs `clientOptions` into both `AISdkClient` and `AISdkClientWrapped` and applies the same reasoning/text-verbosity behavior (`codex` stays "medium"). <sup>Written for commit a9797cc. Summary will update on new commits. <a href="https://cubic.dev/pr/browserbase/stagehand/pull/1852">Review in cubic</a></sup> <!-- End of auto-generated description by cubic. --> <!-- external-contributor-pr:owned source-pr=1844 source-sha=a637dc329bfc5426bb71c8551c812191ed631527 claimer=miguelg719 --> ---------
# why - adds documentation for `setDomainPolicy()` # what changed <img width="1288" height="955" alt="Screenshot 2026-07-13 at 11 07 35 AM" src="https://github.com/user-attachments/assets/3d02d0b2-f7e2-4c33-af88-9469d97d041f" /> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adds docs for `context.setDomainPolicy()` and `context.getDomainPolicy()` to control HTTP(S) requests by domain across a context, with examples and error details. Addresses Linear STG-2426. - **New Features** - Documented `setDomainPolicy(policy: DomainPolicy | null)` and `getDomainPolicy()` with TypeScript signatures, behavior, scope (pages, iframes, subresources), and how to clear. - Clarified domain-only patterns: exact vs wildcard, subdomain rules (`*.example.com` doesn’t match `example.com`), case-insensitive matching, duplicate patterns ignored, and no schemes/paths/ports; `blockedDomains` overrides `allowedDomains`; non-HTTP(S) unaffected; blocked requests show Chrome “BlockedByClient”. - Added a “Domain Policy” example tab using `@browserbasehq/stagehand`, and updated API reference with new errors (`StagehandInvalidArgumentError`, `StagehandSetDomainPolicyError`) plus `V3Context` and `DomainPolicy` interface snippets. <sup>Written for commit 644f32e. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2289?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="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
…ct/observe (browserbase#2359) ## What & why [browserbase#2305](browserbase#2305) silenced the AI SDK v5 "system message in messages" warning, but only for `agent.execute()`'s outer loop (`v3AgentHandler.ts`). `act()`, `extract()`, and `observe()` build their system prompt the same way — a `{ role: "system", ... }` message inside the `messages` array — and route through a separate call path that was never patched: `AISdkClient.createChatCompletion()` in `packages/core/lib/v3/llm/aisdk.ts` (the default client for any `"provider/model"` string), plus the identical pattern in the public BYOC client `packages/core/lib/v3/external_clients/aisdk.ts`. Since the hybrid/DOM agent's own `act`/`extract`/`observe` tools call these primitives internally on every step, an `agent.execute()` run that clicks/types repeatedly still spammed the warning even after upgrading past the first fix — a customer reported exactly this. ### Fix Add `allowSystemInMessages: true` to the 4 remaining `generateObject`/`generateText` call sites (2 in `llm/aisdk.ts`, 2 in `external_clients/aisdk.ts`), same pattern as the original fix. ## E2E Test Matrix | Command / flow | Observed output | Confidence / sufficiency | | --- | --- | --- | | **BEFORE** — published `@browserbasehq/stagehand@alpha` (already includes browserbase#2305's fix), `act()` → `extract()` → `observe()` chain on a real Browserbase session | `AI SDK system-in-messages warnings emitted: YES (4)` | Reproduces the customer's exact symptom on the current alpha — confirms browserbase#2305 left this path unpatched. | | **AFTER** — local patched build (this branch), identical `act()` → `extract()` → `observe()` chain, same model, real Browserbase session | `AI SDK system-in-messages warnings emitted: NO (0)` | Proves the fix removes the warning from all three primitives. | | **AFTER** — local patched build, full chain: `act()` + `extract()` + `observe()` + hybrid `agent.execute()` (multi-step form fill, real Browserbase session) | `AI SDK system-in-messages warnings emitted: NO (0 total)` | Confirms no regression on the already-fixed `agent.execute()` outer loop, and that the agent's internal act/extract/observe tool calls are also silenced end-to-end. | | `pnpm turbo run build --filter @browserbasehq/stagehand` | `Tasks: 3 successful, 3 total` | Typecheck + emit green. | | `eslint` + `prettier --check` on changed files | Both clean | Style/lint gates pass. | A/B model: `anthropic/claude-haiku-4-5-20251001`, `experimental: true`, hybrid agent mode — same harness shape as browserbase#2305's own E2E matrix. Closes STG-2573. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Silences the noisy "system message in messages" warning in `act()`, `extract()`, and `observe()`, including internal tool calls. Sets `allowSystemInMessages: true` at 4 `generateObject`/`generateText` sites and adds unit tests for both AISDK clients; closes STG-2573. <sup>Written for commit d001bd5. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2359?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="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
…ILL.md (browserbase#2334) ## Why The Claude Code eval harness installs a browser skill for the agent it spawns from `packages/evals/skills/browser/SKILL.md` — a 68-line eval-local copy created once in "Evals v2" (browserbase#2011) and never touched again. The real, maintained browse skill lives at `packages/cli/skills/browse/SKILL.md` (355 lines, ships with the CLI). Checking git history: - `packages/evals/skills/browser/SKILL.md`: **1 commit ever** (2026-05-01, browserbase#2011), never edited since. - `packages/cli/skills/browse/SKILL.md`: **10 commits since 2026-06-05**, most recently 2 days ago — roughly one edit every 3 days. That gap already produced a concrete factual error: the most recent CLI-skill commit (browserbase#2296, STG-2450) made `browse snapshot` lean-by-default with `--full` needed for ref maps, but the frozen eval skill still documented the old always-full behavior. It also never mentioned `--verified`/`--proxies`/`--auto-connect`, `browse doctor`, retry discipline, tab/network/cdp/mouse/viewport commands, or Browse.sh skill discovery. The harness was testing an outdated mental model of `browse`. ## Design Single source of truth with an install-time eval addendum: 1. `BROWSER_SKILL_SOURCE` → `BROWSE_SKILL_SOURCE`, now pointing at `packages/cli/skills/browse/SKILL.md` (built from `getRepoRootDir()`, matching the existing `BROWSE_CLI_ENTRYPOINT`-style constants). 2. `installBrowserSkill` → `installBrowseSkill` now reads the CLI skill and inserts a code-level `EVAL_HARNESS_ADDENDUM` template literal **immediately after the YAML frontmatter** (not appended at the end) before writing the combined file. The addendum: - States `browse` is preinstalled/pinned by the harness (no `npm install`, no `--local`/`--remote`/`--session` — the wrapper injects those). - Requires exactly one `browse ...` command per Bash call (shell operators rejected by the harness). - Tells the model to ignore the CLI skill's install/Browse.sh-discovery/cloud/Functions/templates sections — out of scope during evals. - Reiterates no repo edits, no non-`browse` network tools, and the `EVAL_RESULT` reporting format. Why prepend and not append: `isAllowedBrowseCommand` only checks that a Bash command starts with `browse ` and has no shell metacharacters — it does **not** restrict which `browse` subcommand runs. So the addendum's "ignore cloud/functions/skills" instruction is the actual scope-enforcement mechanism, not just a courtesy note, and it needs to be read before the model encounters the CLI skill's concrete (and tempting) examples of those commands, not after. A live smoke run (see below) shows the model reaching for `browse cloud fetch` and `browse skills find` once it got stuck on a bot-protected page — evidence this ordering concern is real, not theoretical. 3. Skill name consistency: installed skill dir renamed `.claude/skills/browser/` → `.claude/skills/browse/` to match the CLI skill's own `name: browse` frontmatter; all harness prompt/log references to "a project skill named browser" updated to "browse". The `stagehand_browser` MCP server name (used by the unrelated `playwright_code`/`cdp_code` tool surfaces) is untouched. 4. Deleted `packages/evals/skills/browser/SKILL.md` and the now-empty `packages/evals/skills/` directory. 5. Updated `packages/evals/tests/framework/claudeCodeToolAdapter.test.ts` for the renamed export/path/skill-name, plus new assertions that the installed file contains both the CLI-skill content and the addendum, with the addendum's string index before `## Cloud APIs`'s index (regression guard against the addendum silently drifting back to append-at-end). **Follow-up (review comment from [ajmcquilkin](browserbase#2334 (comment) the hand-rolled regex in `insertAfterFrontmatter` had already needed a CRLF patch and still failed silently on BOM-prefixed files or a `---` line embedded in a YAML multiline string. Swapped it for [`gray-matter`](https://www.npmjs.com/package/gray-matter) (new `packages/evals` devDependency, private package, no changeset), but only for *boundary detection* — `matter(markdown)` locates where the frontmatter block ends; reassembly still uses the original raw string (`markdown.slice(0, markdown.length - parsed.content.length)` for the frontmatter, `parsed.content` for the body) rather than `matter.stringify()`, since that would re-serialize the YAML through js-yaml and reformat the shipped skill's frontmatter (e.g. its folded `description: >` block). `insertAfterFrontmatter` is now exported and directly unit-tested. No changeset — `packages/evals` is private, eval-infra only. **Overlap note:** this touches `claudeCodeToolAdapter.ts`; open PR browserbase#2299 also touches that file but in a different region (contract fix, not the skill-install path). Trivial rebase for whichever lands second. Linear: [STG-2510](https://linear.app/browserbase/issue/STG-2510/evals-source-browse-skill-from-packagescli-skillmd-instead-of-stale) ## E2E Test Matrix | Command / flow | Observed output | Confidence / sufficiency | | --- | --- | --- | | `pnpm turbo run build --filter=@browserbasehq/stagehand --filter=browse` then `pnpm --dir packages/evals build` | All 4 turbo tasks + evals `build:esm`/`build:cli` completed successfully in `<worktree>` | Confirms the changed adapter compiles against the real CLI/core build artifacts it now depends on (`packages/cli/skills/browse/SKILL.md`, `packages/cli/dist/...`). | | `pnpm --dir packages/evals exec vitest run tests/framework/claudeCodeToolAdapter.test.ts` | `Test Files 1 passed (1)`, `Tests 17 passed (17)` | Covers the renamed export, new install path/skill name, the addendum-ordering assertion, and (as of the gray-matter follow-up below) the frontmatter boundary-detection cases. Narrow to this file. | | `pnpm --dir packages/evals run test:unit` (full evals suite) | `Test Files 48 passed (48)`, `Tests 362 passed (362)` | Confirms no other test in the package depends on the old `browser` skill name/path, `installBrowserSkill` export, or the removed regex helper. | | `node -e` script importing `installBrowseSkill` from built `packages/evals/dist/esm/framework/claudeCodeToolAdapter.js`, run against a temp dir | Installed at `<temp dir>/.claude/skills/browse/SKILL.md`; head matches CLI skill frontmatter (`name: browse`, full description); tail/body contains `## Eval Harness Addendum` positioned before `## Cloud APIs` (indices 1054 vs 9018); frontmatter YAML block intact as the first bytes of the file | Direct artifact proof of the exact shape described above — installed path, single-source content, and addendum placement — independent of the eval harness runtime. | | Live harness run: `EVAL_CLAUDE_CODE_ALLOW_UNSANDBOXED_LOCAL=true EVAL_CLAUDE_CODE_MAX_TURNS=45 node packages/evals/dist/cli/cli.js run b:webtailbench -l 1 -t 1 -c 1 --harness claude_code -e local -m anthropic/claude-haiku-4-5-20251001` (verbose logging on) | Log line `Installed browse skill at <temp dir>/.claude/skills/browse/SKILL.md`; agent then issued `browse open`, `browse snapshot`, `browse doctor`, `browse status`, `browse stop --force`, `browse cloud fetch`, `browse skills find` — every one of them accepted by the harness with no "Only browse commands are allowed" / "Only Skill and Bash are allowed" contract denial anywhere in the log. Task itself ended `error_max_turns` fighting a bot-protected United.com page (unrelated to this change) | Proves the real install → load → drive pipeline works end-to-end with zero contract errors. Task pass/fail is expected to be noisy on this benchmark target and is not the bar here; the pipeline mechanics are what this row proves. | | **Follow-up (review comment):** `pnpm --dir packages/evals build` (esm + cli) after adding `gray-matter` as a devDependency and rewriting `insertAfterFrontmatter` to use it for boundary detection only | Both `build:esm`/`build:cli` completed; `dist/esm/framework/claudeCodeToolAdapter.js` shows `import matter from "gray-matter"` and the new exported `insertAfterFrontmatter` | Confirms the new dependency resolves and the adapter still builds against `packages/cli/skills/browse/SKILL.md`. | | `node` script against the **built** `installBrowseSkill`/`insertAfterFrontmatter` (not source), run in a temp dir | `frontmatter byte-identical to source? true`; text immediately after the frontmatter is only whitespace before `## Eval Harness Addendum` (no leftover body content); `## Cloud APIs` still present after it; direct `insertAfterFrontmatter` calls for the no-frontmatter fallback and an embedded `---` inside a YAML multiline string both produced correctly-bounded output | Real artifact proof (not unit-test mocks) that swapping in gray-matter didn't change the installed file's byte layout — the specific regression the reviewer's suggested library could introduce via `matter.stringify()`, which this implementation deliberately avoids. | | New unit tests in `claudeCodeToolAdapter.test.ts`: LF/CRLF/BOM-prefixed frontmatter, a `---` line inside an indented YAML `>` block, no-frontmatter fallback, unterminated/invalid-YAML fallback (gray-matter throws; now caught), and a byte-identical-to-source frontmatter assertion via `installBrowseSkill` | All pass as part of the 17/17 and 362/362 runs above | Locks in the exact boundary-detection contract the reviewer flagged as fragile; the byte-identical assertion is a standing regression guard against ever switching to `matter.stringify()`. | | `pnpm --dir packages/evals run lint` (prettier + eslint + tsc) | `All matched files use Prettier code style!`; eslint clean; `tsc --noEmit` clean | Confirms the gray-matter typings (`matter.GrayMatterFile<string>`) satisfy the package's strict TS config and formatting rules. | ## Future work A separate in-flight PR (`shrey/cli-skills-show`, not yet on main) adds `browse skills show` (prints the bundled skill to stdout) plus a "Start here (for AI agents)" pointer in `browse --help`, so a real sandboxed agent with no eval scaffolding could self-discover the skill instead of having it handed to it. This PR's shape — inject the real skill + eval addendum into `.claude/skills/browse/` at prepare time — stays the right *default*: it's the conventional eval pattern (benchmarks inject tool docs deterministically, the agent can't skip it), and it mirrors the actual supported CLI workflow (a user who already ran `browse skills install`). It also can't reference `browse skills show` today since that command doesn't exist on main yet. Once `browse skills show` ships, "agent discovers the skill itself via `browse --help`" becomes a good **second, more sandbox-realistic eval arm** (skill-injected vs. self-discovered A/B), not a replacement — worth noting for whoever builds it: `isAllowedBrowseCommand` in this file only checks for a `browse ` prefix and absence of shell metacharacters, so `browse skills show` already passes that gate today with zero adapter changes needed for permissions. The one thing that arm would still need is its own way to deliver the eval-specific overrides (session/environment pinning, one-command-per-call, out-of-scope sections) — `browse skills show` would print the bundled skill verbatim, so that arm likely wants the same `EVAL_HARNESS_ADDENDUM` content delivered via the top-level task prompt instead of a pre-installed skill file. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Switches the eval harness to install the real `browse` skill from `packages/cli/skills/browse/SKILL.md`, injecting a small eval-only addendum right after the frontmatter. Fixes stale guidance and keeps eval behavior aligned with the CLI (Linear STG-2510). - **Bug Fixes** - Source `browse` skill from `packages/cli/skills/browse/SKILL.md` to stop drift and correct outdated docs/behavior. - Insert `EVAL_HARNESS_ADDENDUM` after frontmatter to pin env/session, require one `browse` command per Bash call, and de-scope cloud/functions/templates/skills install. - Parse frontmatter with `gray-matter` to handle BOM/CRLF/embedded `---`, ensuring correct insertion across platforms. - **Refactors** - Rename `installBrowserSkill` → `installBrowseSkill`; install to `.claude/skills/browse/`; update prompts/logs to "browse". Keep `stagehand_browser` MCP name unchanged. - Remove `packages/evals/skills/browser/SKILL.md`. - Export `insertAfterFrontmatter`, add unit tests for boundary cases and addendum-before-"Cloud APIs"; add `gray-matter` as a devDependency. <sup>Written for commit 486cab8. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2334?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="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> ---------
…I publisher (browserbase#2318) ## Why Sonnet 5, GPT-5.6, and Grok eval runs published to the Braintrust UI had no pricing entry, so their cost metrics fell through as unknown. ## What changed Added to the publisher's `MODEL_PRICING_USD_PER_1M_TOKENS` map (prefixed and unprefixed forms per the map's convention): | Model | Input | Cached input | Output | |---|---|---|---| | `claude-sonnet-5` | $3 | $0.30 | $15 | | `gpt-5.6-sol` | $5 | $0.50 | $30 | | `gpt-5.6-terra` | $2.50 | $0.25 | $15 | | `gpt-5.6-luna` | $1 | $0.10 | $6 | | `grok-4.5` | $2 | $0.50 | $6 | | `grok-4.3` | $1.25 | $0.20 | $2.50 | GPT-5.6 keys match the CUA model ids added in browserbase#2343 (`openai/gpt-5.6-{sol,terra,luna}`). Grok rates per docs.x.ai/developers/models (2026-07). Deliberate trade-off (noted in the code comment): Sonnet 5 introductory pricing ($2 / $0.20 / $10) applies through **2026-08-31**, so costs published before then are overstated ~1.5x; standard rates keep the dashboard stable across the cutover instead of requiring a September flip. ## Tests Internal publish script; no behavior beyond the map entries. Typecheck green. ---------
…wserbase#2361) ## Summary `browse` has auto-loaded a `.env` file from the current directory on startup since it moved into this monorepo, with no way to turn it off. A user reported `.env` values in one project being silently overridden by a stale shell-level key from another project -- the shell-level `BROWSERBASE_API_KEY` from an earlier project took precedence over the correct key in a different project's `.env`, so session replays went to the wrong Browserbase account. The agreed direction (per internal eng review) is that CLI tools used across many unrelated projects/directories shouldn't auto-load `.env` at all -- only `process.env` should matter, and it's on the user to `export` vars or source `.env` themselves. Ripping that out in one PR would break the existing install base that relies on the implicit behavior, so this PR is the non-breaking first step: - Default behavior is unchanged: `browse` still loads `.env` by default. - New `BROWSE_LOAD_DOTENV` env var makes that behavior explicitly toggleable: `0`/`false`/`no` (case-insensitive) skips `.env` loading entirely today; any other explicit value (e.g. `1`) keeps loading with no nagging. - When the toggle is left unset (the implicit default) and loading a `.env` file actually applies a variable not already in `process.env`, we print a one-time deprecation warning to stderr naming the variable(s), so people relying on the implicit default get advance notice. A future PR (not this one) will flip the default of `BROWSE_LOAD_DOTENV` to off, once the warning has had time to reach users. This complements browserbase#2360, which adds a `browse doctor` warning when `process.env` and `.env`/`.env.local` values *disagree* -- a useful on-demand diagnostic, but it doesn't touch the auto-load behavior itself. This PR does not depend on browserbase#2360. ## E2E Test Matrix Built the CLI locally (`pnpm turbo run build --filter=browse`) and ran the built `bin/run.js` directly in a scratch temp dir containing a `.env` with a fake `BROWSERBASE_API_KEY`. | Command / flow | Observed output | Confidence / sufficiency | | --- | --- | --- | | `node bin/run.js doctor --json` in scratch dir with `.env` containing `BROWSERBASE_API_KEY=<fake>`, `BROWSE_LOAD_DOTENV` unset | stderr: `[browse] Loaded BROWSERBASE_API_KEY from .env. Auto-loading .env is deprecated and will be disabled by default in a future release -- export these variables in your shell instead, or set BROWSE_LOAD_DOTENV=1 to keep this behavior explicitly once that happens. Set BROWSE_LOAD_DOTENV=0 to opt out today. Run \`browse doctor\` to check for conflicts with your shell environment.` / stdout `verdict: "ok"` / exit 0 | Proves default (implicit) path still loads `.env` and now warns exactly once, naming the applied var. | | `node bin/run.js doctor --remote` in same dir with `BROWSE_LOAD_DOTENV=0` | stdout: `[fail] browserbase BROWSERBASE_API_KEY is not set` / no stderr / exit 1 | Proves opting out today skips `.env` entirely -- the key never reaches `process.env`, no warning printed (explicit choice). | | `node bin/run.js doctor --remote` in same dir with `BROWSE_LOAD_DOTENV=1` | stdout: `[ok] browserbase BROWSERBASE_API_KEY is set` / no stderr / exit 0 | Proves explicit opt-in keeps loading `.env` with zero nagging. | | `pnpm exec vitest run tests/cli-cloud-contract.test.ts tests/doctor.test.ts` | `Test Files 2 passed (2)` / `Tests 69 passed (69)` | Covers the existing `.env`-loads-config contract (unchanged default) plus: warns when unset+applied, silent when `BROWSE_LOAD_DOTENV=1`, and fails auth (key never loaded, no warning) for each of `0`/`false`/`no`/`FALSE`/`NO` as `BROWSE_LOAD_DOTENV` -- added the extra opt-out aliases per cubic review feedback below. | | `pnpm exec eslint bin/run.js tests/cli-cloud-contract.test.ts` + `pnpm exec prettier --check bin/run.js README.md tests/cli-cloud-contract.test.ts` + `pnpm exec tsc --noEmit -p tsconfig.json` | All clean, no output/errors | Static checks on touched files; supporting evidence, not a substitute for the live runs above. | ## Linear STG-2579 (linked via branch name). ---------
…se#2366) ## Summary - replace the redundant “LLM model” wording in the Models page description - keep the existing “Models” page title unchanged ## Why “LLM model” repeats “model,” since LLM already stands for large language model. The updated description reads “Use any LLM with Stagehand.” ## Impact This improves the wording shown in the Models page header and search previews. There is no runtime or API impact. ## Validation - `git diff --check` - confirmed the Models page no longer contains “LLM model” Linear: [GRO-1928](https://linear.app/browserbase/issue/GRO-1928/replace-llm-model-wording-in-stagehand-docs) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Replace “LLM model” with “LLM” in the Models page description to remove redundancy and improve clarity. Title remains “Models”; docs-only change with no runtime or API impact. Addresses Linear GRO-1928. <sup>Written for commit 63ba4a7. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2366?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="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
# Why [OdysseysBench](https://odysseysbench.com) is a 200-task web-agent benchmark (45 easy / 46 medium / 109 hard) where every task ships a **weighted rubric** (weights sum to 1.0). It slots naturally into the rubric-based verifier path — like WebTailBench — so we can score process *and* outcome against the published criteria instead of generating rubrics. # What Changed - **Dataset** (`packages/evals/datasets/odysseysbench/`): committed source snapshot (`source/tasks.json`, mirrored from `https://odysseysbench.com/assets/data/tasks.json`) plus the generated `OdysseysBench_data.jsonl` (200 rows). - **Converter** (`scripts/build-odysseysbench-dataset.ts`): deterministic transform of each task's `rubrics` map → the verifier's `precomputed_rubric` (`{ items: [{ criterion, description, max_points }] }`). Rubric weights scale to integer points (sum ≈ 100; scale is immaterial since the process score is a ratio). Run with `--fetch` to refresh the snapshot. - **Suite** (`suites/odysseysbench.ts`): `buildOdysseysBenchTestcases`, mirroring the WebTailBench suite. Env knobs: `EVAL_ODYSSEYSBENCH_LIMIT` (default 25), `EVAL_ODYSSEYSBENCH_SAMPLE`, `EVAL_ODYSSEYSBENCH_LEVEL` (easy/medium/hard filter), `EVAL_ODYSSEYSBENCH_IDS`. - **Bench task** (`tasks/bench/agent/odysseysbench.ts`): runs the agent through `TrajectoryRecorder` + `V3Evaluator.verify()` with the precomputed rubric. - **Wiring**: dataset fan-out in `index.eval.ts` (respects `EVAL_DATASET=odysseysbench`); `external_agent_benchmarks` category override in `taskConfig.ts` and `cli-legacy.ts`. # How to run ``` pnpm evals --eval-name agent/odysseysbench EVAL_ODYSSEYSBENCH_LEVEL=hard EVAL_ODYSSEYSBENCH_LIMIT=10 pnpm evals --eval-name agent/odysseysbench ``` # Tests - `pnpm --filter @browserbasehq/stagehand-evals run typecheck` — clean - `prettier --check` on all changed files — clean - Dataset fidelity: 200/200 rows; instructions, websites, levels match source; rubric counts + order preserved; all `max_points ≥ 1`; task_ids unique. - Discovery smoke: `agent/odysseysbench` registers under `external_agent_benchmarks`; suite builds testcases with rubric attached; `EVAL_ODYSSEYSBENCH_LEVEL=hard` returns exactly 109 tasks. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adds OdysseysBench as a built-in agent benchmark with precomputed rubrics for outcome and process scoring across 200 web tasks. Tightens env parsing and rubric validation to preserve scoring fidelity and avoid sampling bypasses; fully wires modern CLI and external harness support under `external_agent_benchmarks`. - **New Features** - Dataset: committed source snapshot and generated `OdysseysBench_data.jsonl`; task rubrics converted to verifier `precomputed_rubric`. - Converter: `packages/evals/scripts/build-odysseysbench-dataset.ts` (deterministic; `--fetch` refreshes upstream). - Suite: `packages/evals/suites/odysseysbench.ts` with limit/sample/level/ids knobs. - Bench task: `packages/evals/tasks/bench/agent/odysseysbench.ts` via TrajectoryRecorder + `V3Evaluator.verify()`; success mode via `EVAL_SUCCESS_MODE` (outcome|process|both). - Wiring: dataset fan-out in `packages/evals/index.eval.ts`; category override to `external_agent_benchmarks`; run with `pnpm evals --eval-name agent/odysseysbench`. - **Bug Fixes** - Legacy CLI: register in `packages/evals/evals.config.json` and `packages/evals/cli-legacy.ts` so `b:odysseysbench` resolves. - Modern CLI: register in `packages/evals/tui/commands/parse.ts` and `packages/evals/framework/benchPlanner.ts`, and add `packages/evals/framework/externalHarnessPlan.ts` support so `b:odysseysbench` runs and external harnesses get instruction/startUrl; ensure discovery lists under `external_agent_benchmarks`. - Suite: sanitize `EVAL_MAX_K`/`EVAL_ODYSSEYSBENCH_LIMIT`/`EVAL_ODYSSEYSBENCH_SAMPLE` to prevent NaN from bypassing caps. - Bench task: hard-fail if a task is missing `precomputed_rubric`. - Rubric points: scale weights x1000 to avoid rounding distortion of small criteria. - Converter: validate `task_id`/`confirmed_task`; validate each rubric item (non-empty fields; weight in (0,1]); ensure weights sum to ~1.0; assert row count. <sup>Written for commit 29ccd1a. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2275?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="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> ---------
## Why The `stagehand-server-v3` SEA binary release workflow (`.github/workflows/stagehand-server-v3-release.yml`) only cuts a new tag/binary build when a changeset added since the last `stagehand-server-v3/v*` tag explicitly lists the `@browserbasehq/stagehand-server-v3` package. It doesn't look at `package.json` versions. The last such changeset landed on 2026-06-09 (v3.7.2, browserbase#2217). Since then, 39 changesets have merged that touch `packages/core`/`packages/server-v3` — including Gemini 3.5 Flash CUA support (browserbase#2273) and an AI SDK warning fix affecting act/extract/observe (browserbase#2359) — and none of them included the `stagehand-server-v3` package line. So the release-detect job has returned `release=false` on every push for over a month, even though `updateInternalDependencies: patch` cosmetically bumps server-v3's `package.json`/CHANGELOG on every Version Packages PR, making it look like a release happened when it didn't. This changeset is a one-time catch-up: it doesn't change any code, it just gives the release workflow a qualifying trigger so it cuts a binary build containing everything already merged to `packages/core` since v3.7.2. Related: a user asked about this gap on Discord, referencing browserbase#2333 (Gemini 3.5 Flash support request). ## What changed - Added `.changeset/stagehand-server-v3-catchup-release.md` bumping `@browserbasehq/stagehand-server-v3` (patch, 3.7.2 → 3.7.3). ## E2E Test Matrix | Command / flow | Observed output | Confidence / sufficiency | | --- | --- | --- | | Ran the exact front-matter regex from `stagehand-server-v3-release.yml`'s `detect` job against the new changeset file | `Parsed: @browserbasehq/stagehand-server-v3 patch matches target package: true` | Proves this changeset satisfies the workflow's own detection logic and will set `release=true` on merge to main, advancing the tag from `v3.7.2` to `v3.7.3`. | | Verified all 13 historical `stagehand-server-v3` tags against their triggering commit's changeset | 13/13 correlate exactly with a changeset explicitly bumping `@browserbasehq/stagehand-server-v3` | Confirms the detection mechanism is real and consistent — this isn't a guess about how the pipeline works. | | Scanned all 39 changesets added to main since the `v3.7.2` tag commit | 0/39 include a `stagehand-server-v3` line | Confirms the gap is total (not partial) and this PR is the correct/only trigger needed. | Linear: [STG-2587](https://linear.app/browserbase/issue/STG-2587/cut-a-stagehand-server-v3-release-to-catch-up-on-binary-drift-since) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Triggers a catch-up SEA binary release for `@browserbasehq/stagehand-server-v3` by adding a changeset, so all core changes since v3.7.2 ship (including Gemini 3.5 Flash computer-use support). Addresses Linear STG-2587 by closing the drift between version bumps and actual binary releases. <sup>Written for commit efb5f99. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2367?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="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
…riments (browserbase#2177) ## What Stacked on browserbase#2138. - Persist trajectories under `<root>/<experiment>__<model>/<task.id>/<runId>/` instead of scattered per-task timestamp dirs, so a run's trajectories live in one folder and concurrent multi-model runs of the same suite never interleave on disk. - Write a `metadata.json` into every trajectory dir (experiment, model, provider, environment, task, runId, status) so a trajectory's run never has to be reverse-engineered after the fact. - Write an `experiment.json` at the group root cross-linking the local trajectories to the resolved Braintrust experiment (hashed name, id, project, URLs) once `Eval()` resolves. - New `framework/trajectoryGroup.ts` owns the slugging/layout/metadata helpers; `TrajectoryRecorder`, the external-harness persister, and both eval entrypoints (`index.eval.ts`, `framework/runner.ts`) consume it. ## Why Mapping on-disk trajectories back to their Braintrust experiment previously required guessing by timestamp, and concurrent runs of different models wrote into the same root with no on-disk marker — making post-hoc analysis error-prone. ## Notes - Local persistence only; Braintrust experiment naming/metadata is unchanged. - Tooling that globs `.trajectories/*` keeps working (one extra path level); the trajectoryRecorder unit test asserts the new layout + `metadata.json`. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Groups eval trajectories by experiment, unambiguous model, and a run-unique token, and cross-links each run to its Braintrust experiment. Adds atomic dir reservation, single-flight caching, and env hygiene so runs never overwrite, mislabel, or leak; the run token uses 64‑bit entropy and result persistence is order-free (also trims internal comments; no logic changes). - **New Features** - Store trajectories at `<root>/<experiment>[__<model>]__<runToken>/<task.id>/<runId>/`; write `metadata.json` per trajectory and an `experiment.json` at the group root linking to the resolved Braintrust experiment; implemented in `framework/trajectoryGroup.ts` and used by the recorder, adapter persister, runner, and `index.eval.ts`. - Generate one run token per run (64‑bit entropy); derive the group/metadata model from the actual testcase matrix when unambiguous (`EVAL_TRAJECTORY_MODEL`), not the requested override; stamp `EVAL_EXPERIMENT_NAME`/`EVAL_TRAJECTORY_GROUP`; `writeExperimentLink()` takes an explicit group, respects `EVAL_TRAJECTORY_ROOT`, and only writes if persistence is on and the group dir exists. - Bench hardening: add `taskDates.ts` for safe rolling dates (WebMD); update Healthline to stop before final subscribe; clarify Google Maps rubric; KFC requires pickup scheduling; SFPL uses the live application URL; default `maxSteps`: `agent/sf_library_card` 1→10, `agent/sf_library_card_multiple` 20→25. TUI `verify` help and docs reflect the new layout. - **Bug Fixes** - Prevent clobbering by atomically reserving trajectory dirs (`reserveTrajectoryDir` adds `-2/-3` on collision) with single-flight caching in the recorder; `finish()` is idempotent; result persistence is order-free (now covered by tests); `metadata.json` records `runDir` and `attempt`. - Hardened slugging: reject pure-dot values (e.g., `"."`, `".."`) to avoid path escapes; fall back to the `"default"` group. - REPL env hygiene: `withEnvOverrides()` snapshots/restores run-stamped env (`EVAL_TRAJECTORY_GROUP`, `EVAL_EXPERIMENT_NAME`, `EVAL_TRAJECTORY_MODEL`) so runs don’t leak between commands; recorder and adapter both honor `EVAL_TRAJECTORY_ROOT`. - Trimmed internal comments and moved a misplaced docstring; no logic changes. <sup>Written for commit 2d17642. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2177?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="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> ---------
## Summary - Add Kyle Jeong as CODEOWNER for docs and Markdown changes. - Add the docs styling guide to the repo's agent instruction files. - Configure Cubic with a file-backed docs style custom rule so PR reviews actively flag violations. ## Validation - `git diff --check` - Parsed `cubic.yaml` locally with Ruby YAML - Confirmed `.cubic/docs-style-guide.md` is under Cubic's 10,000-character custom-agent limit
…rowserbase#2347) ## Summary - add `openaiEndpointFormat` to public model configuration - select OpenAI Chat Completions when `openaiEndpointFormat` is `chat` - preserve Responses API as the default - omit the Responses-only `store` option for Chat providers, including finalization calls - add a patch changeset ## Usage ```ts const stagehand = new Stagehand({ model: { modelName: "openai/databricks-claude-opus-4-6", apiKey: process.env.DATABRICKS_TOKEN, baseURL: "https://example.databricks.com/serving-endpoints", openaiEndpointFormat: "chat", }, }); ``` The option names the *wire format* used against the OpenAI-compatible endpoint at `baseURL` — deliberately not `apiMode` (too generic) or `openaiEndpoint` (reads like it holds a URL, and sits right next to `baseURL`). ## Databricks verification - basic Chat Completions request: 200 - request with `store: false` reproduced the 400: extra inputs are not permitted - two-turn tool call and tool-result replay without `store`: 200 on both turns ## Testing - focused llm-provider and agent suites: 19 passed - TypeScript typecheck - ESLint on touched TypeScript files - Prettier check on touched files
Propagates openaiEndpointFormat through the server-v3 request schema and generated OpenAPI spec so Stainless can expose it in the Python SDK. Adds a regression test covering request and session initialization parsing Validation: - core build - server-v3 OpenAPI generation - server-v3 typecheck - requestModelConfig unit tests (9 passing) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Expose the OpenAI endpoint wire format in v3 via `openaiEndpointFormat` on model config and OpenAPI, so SDKs can target `responses` or `chat` (defaults to Responses). Adds a unit test to ensure the format is preserved through request parsing and session initialization, and a changeset to release in `@browserbasehq/stagehand` and `@browserbasehq/stagehand-server-v3`. <sup>Written for commit 6f8dedb. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2374?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="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
…ase#2369) ## Why `e2e/local/chrome-newtab-page-tracking` fails intermittently on unrelated PRs (e.g. [this run on browserbase#2347](https://github.com/browserbase/stagehand/actions/runs/29538363318/job/87758303923?pr=2347)). This removes the cause. ## What changed Updated `chrome://new-tab` to go to `chrome://version` from a static injected site ## Test plan - [x] get CI fully green again
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@3.7.1 ### Patch Changes - [browserbase#2359](browserbase#2359) [`2cd1edf`](browserbase@2cd1edf) Thanks [@shrey150](https://github.com/shrey150)! - Remove the noisy AI SDK "system message in messages" warning from `act()`, `extract()`, and `observe()` (including when the agent's own tools call them internally). - [browserbase#2347](browserbase#2347) [`84197d8`](browserbase@84197d8) Thanks [@miguelg719](https://github.com/miguelg719)! - Allow OpenAI-compatible models to select the Chat Completions API with `openaiEndpointFormat: "chat"` ## @browserbasehq/stagehand-evals@2.1.0 ### Minor Changes - [browserbase#2275](browserbase#2275) [`cdae405`](browserbase@cdae405) Thanks [@miguelg719](https://github.com/miguelg719)! - Add OdysseysBench as a supported agent benchmark in the evals CLI. OdysseysBench is a 200-task web-agent benchmark (45 easy / 46 medium / 109 hard); each task ships a weighted rubric that is baked into the verifier's `precomputed_rubric` format so process + outcome are scored against the published criteria. Run with `--eval-name agent/odysseysbench` (or the `external_agent_benchmarks` category); supports `EVAL_ODYSSEYSBENCH_LIMIT`, `EVAL_ODYSSEYSBENCH_SAMPLE`, `EVAL_ODYSSEYSBENCH_LEVEL`, and `EVAL_ODYSSEYSBENCH_IDS`. ### Patch Changes - Updated dependencies \[[`2cd1edf`](browserbase@2cd1edf), [`84197d8`](browserbase@84197d8)]: - @browserbasehq/stagehand@3.7.1 ## @browserbasehq/stagehand-server-v3@3.7.3 ### Patch Changes - [browserbase#2347](browserbase#2347) [`84197d8`](browserbase@84197d8) Thanks [@miguelg719](https://github.com/miguelg719)! - Allow OpenAI-compatible models to select the Chat Completions API with `openaiEndpointFormat: "chat"` - [browserbase#2367](browserbase#2367) [`a985943`](browserbase@a985943) Thanks [@shrey150](https://github.com/shrey150)! - Cut a new stagehand-server-v3 SEA binary release to catch up with recent core changes, including Gemini 3.5 Flash computer-use support. - Updated dependencies \[[`2cd1edf`](browserbase@2cd1edf), [`84197d8`](browserbase@84197d8)]: - @browserbasehq/stagehand@3.7.1
## What Adds `--only-errors` (and `--failed-requests`) to `browse cloud sessions logs`. By default the command returns the full CDP firehose (~hundreds of events, unchanged). `--only-errors` runs a deterministic reducer that returns just the high-signal error records: - console errors / warnings / asserts - uncaught exceptions (with app-frame-trimmed stacks) - HTTP 4xx/5xx responses - net-level load failures (CORS / DNS / connection) deduped, no LLM. ``` browse cloud sessions logs <id> --only-errors browse cloud sessions logs <id> --only-errors --failed-requests ``` ## Why Agents debugging Browserbase sessions (build/verification agents for AI app builders) want the runtime errors, not the raw firehose. Today they pull ~hundreds of CDP events and grep. `--only-errors` returns the handful that matter in one call — far fewer tokens/tool-calls in the agent loop, and language-agnostic (shell out from any agent). ## Scope / notes - **Default behavior unchanged** (raw firehose) — opt-in only, so no breaking change. - Reducer lives in `packages/cli/src/lib/cloud/reduce-logs.ts` (pure, unit-testable). - Catches console / exception / 4xx-5xx / net-failure classes. Does **not** catch an HTTP 200 response carrying an error *body* (that needs response-body capture at ingest — follow-up). 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Add --only-errors to cloud sessions logs to return only high-signal errors, with an optional --failed-requests to narrow to failed network calls. Default output is unchanged. - **New Features** - `--only-errors`: returns console errors/warnings/asserts, uncaught exceptions (trimmed stacks), HTTP 4xx/5xx, and network load failures; deduped. - `--failed-requests`: with `--only-errors`, returns only failed/error-status network requests. - Deterministic reducer added in `packages/cli/src/lib/cloud/reduce-logs.ts` (pure and unit-testable). <sup>Written for commit 88c785f. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2373?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="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
# why `model: "auto"` shipped in browserbase#2328 but is undocumented. This adds a **Model Router** section to `/v3/configuration/models`, nested under **Model Gateway** right after **Setup**, plus a launch callout at the top of the page. # what changed `packages/docs/v3/configuration/models.mdx` only — no code, so no changeset. - **`(NEW) Model Router` callout** at the top of the page, above the Model Gateway one, linking to `#model-router`. Dropped `(NEW)` from the Model Gateway callout so only one feature is flagged as new. - **`### Model Router`** subsection: what it does, basic usage, Model Gateway billing (market-price tokens on your Browserbase key), and the 30–40% cost reduction vs. pinning one frontier model. - **`#### Requirements`** table covering the four API-only constraints the constructor enforces (`env: "BROWSERBASE"`, `disableAPI: false`, `experimental: false`, no `llmClient`), plus a warning that `"auto"` has no local fallback if the API is unavailable at init. - **`#### Per-call routing`**: `{ model: "auto" }` as a per-primitive override on top of a concrete default, including the object form (`{ modelName: "auto", temperature: 0.5 }`) and the note that overrides don't inherit the session provider/API key. - Agent support: works in default `dom`/`hybrid`, **not** with `mode: "cua"` (`"auto"` isn't in `AVAILABLE_CUA_MODELS`, so it throws `CuaModelRequiredError`). - Caching: local replay is skipped for `"auto"` sessions; the API's server-side cache handles it. - Troubleshooting accordion for both auto-specific errors, and an env-var table row. Behavior claims were taken from `modelUtils.ts`, `v3.ts`, `api.ts`, `AgentCache.ts`, and `tests/unit/auto-model.test.ts`. The routing heuristic itself is server-side and not in this repo, so the copy deliberately stays general about how a model gets picked — nothing here goes stale when the router changes. # open question Should omitting `model` entirely also route through Model Router? Today it doesn't — `resolveModelConfiguration()` falls back to `DEFAULT_MODEL_NAME = "openai/gpt-4.1-mini"` (`packages/core/lib/v3/v3.ts:101,115`), so an omitted `model` pins gpt-4.1-mini (run through Model Gateway if only a Browserbase key is set). That's a fixed model, not per-call routing. I left the omit path out of the docs for now. If the default should become `"auto"`, that's a core change and the docs can follow alongside it. # test plan - `mintlify broken-links` — no new broken links (the 5 reported are pre-existing and in other files) - JSX components balanced, code fences even, heading nesting verified, `#model-router` anchor resolves ---------
## Summary
- translate daemon socket `ECONNREFUSED` and `ENOENT` failures into a
human-readable error
- print the exact `browse open` command that restarts the requested
session
- document that API keys are forwarded to an already-running daemon and
that `browse stop` is idempotent
- preserve regression coverage for late environment variables and
exit-code-zero cleanup
## Root cause
The daemon client passed raw Unix socket errors through to users when
the daemon disappeared between the readiness check and the request.
Agents received `ECONNREFUSED` without a recovery command.
## Impact
Agents now get an actionable `daemon_not_running` failure with the exact
command needed to restart the session. Recovery command arguments are
shell-quoted, and a daemon disappearing between status and stop is
treated as an already-stopped session. Cleanup synchronizes with daemon
startup so it preserves a replacement daemon that starts during the stop
race. The bundled SKILL.md also makes the already-fixed env timing and
stop behavior explicit.
## E2E Test Matrix
| Command / flow | Observed output | Confidence / sufficiency |
| --- | --- | --- |
| `pnpm --filter browse build` | TypeScript compilation and oclif
manifest generation completed successfully. | Proves the exact local CLI
code under review builds; does not exercise a live browser. |
| `pnpm --filter browse test:cli` | 25 test files passed; 366/366 tests
passed. This includes deterministic daemon disappearance/restart races,
adversarial recovery-command shell quoting, malformed CDP log payloads,
and the complete CLI contract suite. | Proves the full CLI suite passes
against the completed local build on macOS; GitHub CI provides the
Ubuntu/Windows matrix. |
| `BROWSE_DAEMON_DIR=<temp dir> node packages/cli/bin/run.js stop
--session no-daemon-smoke` | Exited `0` and printed `{ "stopped": false,
"session": "no-daemon-smoke" }`. | Exercises the built CLI's real
absent-daemon stop path and confirms its documented successful no-op
behavior. |
| `pnpm --filter browse lint` | Full-package Prettier, ESLint, and
TypeScript checks completed successfully. | Reproduces the CI lint
pipeline locally and verifies the daemon changes, log reducer, tests,
and inherited CLI lint baseline. |
Linear:
[GRO-1908](https://linear.app/browserbase/issue/GRO-1908/cli-namespace-2027-dev-suggested-docs-updates)
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Replaces raw socket errors with a clear `daemon_not_running` message and
prints the exact, shell-quoted `browse open` recovery command. `browse
stop` now treats a missing daemon as already stopped, validates
ownership before cleanup, and avoids tearing down a daemon that restarts
mid-race. Addresses Linear:
https://linear.app/browserbase/issue/GRO-1908/cli-namespace-2027-dev-suggested-docs-updates.
- **Bug Fixes**
- Map `ECONNREFUSED`/`ENOENT` to `daemon_not_running` and show the
precise, session-aware recovery command.
- Make `browse stop` idempotent and safe: if the daemon vanished (no
`--force`), return `{ stopped: false }`; acquire a lock and verify
daemon ownership before removing pid/socket/lock; preserve a replacement
daemon started during the stop race (handles PID reuse).
- Harden CDP log reduction: stricter parsing, ignore malformed payloads,
and require numeric response statuses.
- Docs: clarify `BROWSERBASE_API_KEY` is forwarded on every command and
`browse stop` is idempotent; add troubleshooting for the new error.
- Tests: cover the error message and shell-quoted recovery command
(round-tripped through `/bin/sh`), stop race cleanup and restart
preservation, PID reuse, and reduce-logs edge cases.
<sup>Written for commit 030c02a.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/browserbase/stagehand/pull/2356?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="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
---------
Prepare the next browse release by versioning the package on `main`. What this PR does: - bumps `packages/cli/package.json` to `0.9.6` - updates the browse changelog - consumes the pending browse changesets After this PR merges, the `Release` workflow on `main` will publish `browse@0.9.6` from that exact commit using `pnpm pack` + `npm publish --provenance`.
Addresses browserbase#163 with setup, PR expectations, and fork CI notes.
|
|
This PR is from an external contributor and must be approved by a stagehand team member with write access before CI can run. |
There was a problem hiding this comment.
All reported issues were addressed across 2 files
Architecture diagram
sequenceDiagram
participant Dev as External Contributor
participant Repo as GitHub Repository
participant CI as CI Pipeline
participant Maintainer as Maintainer
Note over Dev,Maintainer: NEW: External Contribution Flow
Dev->>Repo: Fork & clone repo
Note over Dev: Local Development Setup
Dev->>Dev: Run pnpm install
Dev->>Dev: Run pnpm run build
Dev->>Dev: Run pnpm run example
Dev->>Dev: Run lint/format checks
alt Local tests
Dev->>Dev: Run pnpm run test:core/local
Dev->>Dev: Run pnpm run test:e2e/local
Dev->>Dev: Copy .env.example for credentials
end
Dev->>Repo: Create focused branch
Dev->>Repo: Submit Pull Request
alt External Contributor PR
Repo->>CI: Trigger fork CI approval handoff
CI->>Maintainer: Request CI approval
Maintainer->>CI: Approve CI run
CI->>Repo: Run full CI pipeline
CI-->>Dev: Build/lint/test results
else Internal PR
Repo->>CI: Run full CI automatically
CI-->>Dev: Build/lint/test results
end
Maintainer->>Repo: Review PR
alt PR Approved
Repo->>Repo: Merge PR
Repo-->>Dev: Contribution accepted (MIT License)
else Changes Requested
Repo-->>Dev: Feedback
Dev->>Repo: Push updates
end
Note over Dev: Bug Reporting Path
Dev->>Repo: Use bug report template
Repo-->>Dev: Issue created with version/browser/env
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Match package.json engines (^20.19.0 || >=22.12.0) and point the example script at packages/core/examples/example.ts. Signed-off-by: Felipe Fernandes <felipe.of.dev@gmail.com>
Addressed cubic review
— Felipe Fernandes · Systems & Agentic AI Engineer |
1a2f5b7 to
d1364a2
Compare
Summary Adds a root
CONTRIBUTING.mdso external contributors have a clear path beyond the short README note. ## Changes - Development setup (pnpm install/build/example) - Priority guidance (reliability  extensibility  speed  cost) - PR expectations + note about fork CI approval handoff - Bug report pointers - Links README Contributing section to the new guide Fixes #163 ## Test plan - [ ] Skim CONTRIBUTING.md for accuracy vs current scripts in root package.json - [ ] Confirm Discord / priority wording still matches maintainer intentSummary by cubic
Adds a root CONTRIBUTING.md and overhauls the contributor workflow: clear guidelines, issue/PR templates, external‑contributor CI handoff, and formatting/linting hooks. Converts the repo to a pnpm monorepo and introduces a new
browseCLI package with release workflows; fixes #163.pnpm, test scripts, PR expectations (incl. fork CI approval handoff), and bug reporting; README links to it..env.example, updated.gitignore; Node.js^20.19.0or>=22.12.0enforced.packages/cli(browse) package (commands, docs, Dockerfile) plus Changesets config, CHANGELOG, and LICENSE.Written for commit d1364a2. Summary will update on new commits.
— Felipe Fernandes · Systems & Agentic AI Engineer
https://github.com/felipeofdev-ai · https://felipeofdev-ai.github.io/