[STG-2192] fix(cli): make browse skills add failures diagnosable + fail cleanly on unknown skill - #2210
Merged
Merged
Conversation
…on unknown skill `browse skills add <domain>/<task>` previously fell back to git-cloning the entire browse.sh repo whenever the catalog file API returned 404, then exited with an opaque "No matching skills found" / EEXIT 1 whose real cause never reached telemetry. - Capture the `npx skills add` child's stdout/stderr (tail) while still streaming it live, so a nonzero exit surfaces the real reason in the error message and in telemetry instead of a bare exit code. - Detect skill-not-found before the slow clone fallback: a definitive 404 for a non-generated id now fails with an actionable message pointing at `browse skills find` / `browse skills list`. Suffix-shaped (generated) ids and unavailable-catalog cases keep the browse.sh clone fallback. - Emit distinct telemetry result codes: skill_not_found, invalid_skill_id, npx_missing, skill_install_failed. - Replace oclif's bare "Missing 1 required arg" with actionable guidance when no skill id is passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 84d9040 The changes in this PR will be included in the next version bump. This PR includes changesets to release 0 packagesWhen changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor
There was a problem hiding this comment.
1 issue found across 5 files
Confidence score: 4/5
- This PR is likely safe to merge with minimal risk: the reported issue is moderate (5/10) and appears limited to error classification rather than core install execution.
- In
packages/cli/src/lib/skills/install.ts, errors thrown byspawnPassthroughcan bypassfail(...), so failed installs may not be tagged asskill_install_failed, which can reduce consistency of downstream handling and diagnostics. - Given the high confidence (8/10), this is worth a follow-up fix, but it does not strongly indicate a broad functional break across the reviewed changes.
- Pay close attention to
packages/cli/src/lib/skills/install.ts- ensurespawnPassthroughfailures are routed throughfail(...)for consistentskill_install_failedclassification.
Architecture diagram
sequenceDiagram
participant User as CLI User
participant Cmd as SkillsAdd Command
participant Install as installSkill()
participant Parse as parseSkillId()
participant FetchAPI as fetchSkillFilesFromApi()
participant GitHub as browse.sh Repo
participant Npx as npx skills add
participant Telemetry as Telemetry (PostHog)
User->>Cmd: browse skills add <rawId>
alt No argument
Cmd->>Cmd: validate args
Cmd->>Cmd: fail("Missing skill id...", 2, invalid_skill_id)
Cmd->>Telemetry: commandCompleted (result_code=invalid_skill_id)
end
Cmd->>Install: installSkill(rawSkillId)
Install->>Parse: parseSkillId(rawSkillId)
alt Invalid format (e.g. "../bad")
Parse->>Parse: fail("Invalid skill id...", 1, invalid_skill_id)
Parse->>Telemetry: commandCompleted (result_code=invalid_skill_id)
end
alt npx not found
Install->>Install: findExecutable("npx")
note over Install: Checks PATH
alt Not found
Install->>Install: fail("npx not installed...", 1, npx_missing)
Install->>Telemetry: commandCompleted (result_code=npx_missing)
end
end
Install->>FetchAPI: fetchSkillFiles(skillId)
alt API returns 404 AND id is non-generated (no suffix)
FetchAPI->>FetchAPI: return { status: "not_found" }
Install->>Install: fail("Skill not found...", 1, skill_not_found)
Install->>Telemetry: commandCompleted (result_code=skill_not_found)
else API returns 404 AND id has generated suffix
FetchAPI->>GitHub: check directBlobSkillExists()
alt Blob exists
note over FetchAPI: Fallback to direct blob URL
FetchAPI->>Install: return { status: "found", files }
else Blob missing
FetchAPI->>Install: return { status: "fallback" }
Install->>Npx: runSkillsInstall(npx, [clone browse.sh --skill])
end
else API returns found files
FetchAPI->>Install: return { status: "found", files }
Install->>Install: downloadBlobSkill()
Install->>Npx: runSkillsInstall(npx, ["--yes", "skills", "add", <localPath>])
else API unavailable
FetchAPI->>FetchAPI: return { status: "unavailable" }
alt Id has generated suffix
FetchAPI->>GitHub: check directBlobSkillExists()
note over GitHub: Same path as API 404 + suffix
else Non-generated id
FetchAPI->>Install: return { status: "fallback" }
end
end
Npx->>Npx: spawnPassthrough() — pipes stdout/stderr live + buffers tail
alt npx exits 0
Npx->>Cmd: return { exitCode: 0 }
Cmd->>User: "Downloaded N skill file(s) to..."
Cmd->>Telemetry: commandCompleted (success=true)
else npx exits nonzero
Npx->>Install: return { exitCode: N, output: "...tail..." }
Install->>Install: fail("Could not install skill: ...", exitCode, skill_install_failed)
Install->>User: stderr with captured detail
Install->>Telemetry: commandCompleted (result_code=skill_install_failed)
end
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
spawnPassthrough rejected on child 'error' events, so spawn failures (e.g. EACCES executing npx) escaped runSkillsInstall's classification and surfaced as unclassified runtime errors. Resolve with a nonzero exit + the error message instead, so they're recorded as skill_install_failed. Addresses cubic review comment on #2210. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ziruihao
approved these changes
Jun 8, 2026
Kylejeong2
approved these changes
Jun 9, 2026
This was referenced Jun 12, 2026
shrey150
added a commit
that referenced
this pull request
Jun 17, 2026
…-failure backoff (#2248) ## Summary Makes browse driver (browser session) failures actionable, classified, and self-correcting. Today an invalid `BROWSERBASE_API_KEY` surfaces a bare `Error: 401 Unauthorized` with no remediation, the 5s init-failure cache makes every retry instant and identical, and most driver failures reach telemetry as `unexpected`. Linear: [STG-2277](https://linear.app/browserbase/issue/STG-2277/make-browse-driver-errors-actionable-with-result-codes-and-init) ## Impact if merged This targets the browse CLI's largest failure mode by volume and by user pain. 71 installs stuck in get/screenshot retry loops generate 92.3% of ALL CLI telemetry (~5.5M events/30d); ~375k of those events come from tagged claude-code and codex agents on current versions — exactly the target ICP (coding agents driving browsers). Root cause (smoke-tested): any `BROWSERBASE_API_KEY` forces remote mode; an invalid key surfaces a bare `Error: 401 Unauthorized` with no remediation, and the 5s init-failure cache makes every retry instant, so agents can't self-correct and loop forever. Separately, 2,337 distinct users hit missing_api_key/auth_401 in 30d, and `open` — whose failures are 94% unclassifiable today (result_code `unexpected`) — gates activation: only 28.5% of real users reach an activated session, and a failed first command cuts 7-day retention 12.4x. This PR makes auth/driver failures actionable (agents recover in one turn) and classified (we can finally measure why open fails). ## Implementation notes - **Remote init classification** (`remote.ts`): new `classifyRemoteInitError()` duck-types the SDK error's `status` — 401 → `remote_auth_401` (invalid-key message with settings link, `--local`, `browse doctor`), 403 → `remote_auth_403` (permissions/plan wording, same escape hatches), other → `remote_session_create_failed` (original message preserved + `browse doctor` pointer). Wired through the `RemoteCapability` interface so local-only builds compile. - **Chrome-not-found** (`session-manager.ts`): chrome-launcher's `ERR_LAUNCHER_NOT_INSTALLED` / `ERR_LAUNCHER_PATH_NOT_SET` failures in managed-local mode get install/`--cdp`/remote guidance instead of leaking launcher internals. - **Init-failure backoff**: cached init failures now back off exponentially — `min(5s * 2^(n-1), 5min)` — reset on success and `close()`. After ≥3 consecutive failures the cached message gains a `(failing repeatedly — fix BROWSERBASE_API_KEY, use --local, or run browse doctor)` suffix (deduped on rethrow). - **Result codes over the daemon protocol**: `ErrorResponseSchema` gains optional `code`/`httpStatus` (backward compatible — old daemons omit them); the daemon's `formatError` surfaces them from typed `DriverError`s; the client rethrows as `CommandFailure` with `resultCode`/`httpStatus` so the existing #2210 telemetry plumbing records them. Client-side fail sites tagged: `daemon_lock_timeout`, `daemon_unresponsive`, `daemon_socket_timeout`, `daemon_spawn_failed`. Already-authored driver errors tagged: `stale_ref` (unknown ref), `no_active_page`. - **Local-only build contract preserved**: remediation strings that mention `BROWSERBASE_API_KEY` live behind the remote capability (`driverInitHints()`), so the `build:local-only` artifact stays key-free (guarded by the existing `local-only-build.test.ts`, which caught the first draft). ## E2E Test Matrix | Command / flow | Observed output | Confidence / sufficiency | | --- | --- | --- | | `BROWSERBASE_API_KEY=bb_invalid_test <local build> get url` | `Browserbase rejected your BROWSERBASE_API_KEY (401 Unauthorized). A set key makes browse default to remote mode. Check the key at https://browserbase.com/settings, run without one using --local (browse open <url> --local), or diagnose with browse doctor.` exit=1 | Proves the new 401 classification flows daemon → protocol → client → stderr end-to-end against the real Browserbase API. | | Same command 4x rapidly (cached failure window) | Identical actionable message each time, ~400ms per run (no remote round-trip) | Proves cached failures keep the actionable message and stay instant; does not by itself prove backoff growth. | | Same command after 6s, then after 11s more (real failures #2, #3) | Message gains ` (failing repeatedly — fix BROWSERBASE_API_KEY, use --local, or run browse doctor)` suffix, exactly once, exit=1 | Proves the ≥3-consecutive-failures hint and suffix dedupe on the live failure path. | | Valid key: `open https://example.com` → `get title` → `stop` | `"mode": "remote" ... "title": "Example Domain"`, then `{"title": "Example Domain"}`, then `{"stopped": true}` | Proves the remote happy path is unchanged (no regression in outputs or exit codes). | | `env -u BROWSERBASE_API_KEY <local build> open https://example.com --local` → `get url` | `"mode": "managed-local" ... "url": "https://example.com/"`, then `{"url": "https://example.com/"}` exit=0 | Proves keyless managed-local mode is unaffected. | | `get text @9-99` on the local session | `Unknown ref "9-99" - run browse snapshot first to populate refs (have 0 refs).` exit=1 | Proves the stale-ref message is unchanged while now carrying `stale_ref` through the protocol (round-trip unit-tested). | | `browse doctor` with and without key | `Status: ok` in both; `target remote` with key, `target managed-local` without | Proves doctor behavior unchanged. | | `pnpm build` + `pnpm lint` (prettier, eslint, tsc) | All pass | Supporting only. | | `pnpm test:cli` | 16 files / 228 tests pass, incl. new `driver-errors.test.ts` (classification, backoff schedule, chrome-not-found detection, protocol round-trip, key-free local-only hints) and the `local-only-build` artifact guard | Supporting; covers mappings and the local-only security contract not exercised by live smokes. | 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Makes browse driver failures actionable and self-correcting with classified result codes and exponential init backoff. Addresses Linear STG-2277 by giving clear fixes for bad `BROWSERBASE_API_KEY`, missing Chrome/Chromium, and daemon issues, while improving telemetry. - **New Features** - Classify remote init errors into actionable messages with codes: `remote_auth_401`, `remote_auth_403`, `remote_session_create_failed` (with links to settings, `--local`, and `browse doctor`). - Add error result codes to the daemon protocol (`code`, `httpStatus`) and propagate to the client for telemetry. - Exponential backoff for cached init failures (5s doubling, capped at 1 minute) with a “failing repeatedly” hint after 3 failures. - Tag common failures with stable codes: `daemon_lock_timeout`, `daemon_unresponsive`, `daemon_socket_timeout`, `daemon_spawn_failed`, `stale_ref`, `no_active_page`, `no_chrome_found`. - Use `http-status-codes` for status mapping and extract chrome-launcher error codes to a constant (no behavior change). - **Bug Fixes** - Chrome-not-found now gives Chromium-first guidance: Linux `apt install chromium`; macOS `brew install --cask google-chrome` or set `CHROME_PATH` for Chromium, plus `--cdp` or remote as options. - Keep the local-only build key-free by moving `BROWSERBASE_API_KEY` remediation strings behind the remote capability. <sup>Written for commit b7a3f7e. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2248?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. --> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
shrey150
added a commit
that referenced
this pull request
Jun 25, 2026
…tages (#2250) ## Summary Linear: https://linear.app/browserbase/issue/STG-2279/fix-windows-skills-add-npx-quoting-and-bound-installer-timeouts Fixes `browse skills add` on Windows (cmd.exe spawn quoting) and bounds the two unbounded skills-installer stages (the `npx skills add` child and the catalog/file fetches). ## Impact if merged skills.add succeeds for 3.2% of Windows users (54 in the last 30d) — effectively broken for every default `C:\Program Files\nodejs` Node install, because the npx child is spawned through cmd.exe with an unquoted path. Windows devs are a smaller slice of the CLI base, but skill installers are the single highest-value cohort in our telemetry: 7x the engagement (median 12.5 vs 2 commands) and 19x the multi-day retention (28.4% vs 1.5%) of non-installers, and skills.find→add is an agent-facing funnel (51% of finders attempt an install within an hour). This also bounds the two unbounded installer stages (npx child: 180s; catalog fetches: 10s) — today they can hang forever, feeding the slow-failure retry loops that dominate telemetry volume. ## Implementation notes **Root cause.** `findExecutable` resolves `npx` via PATH+PATHEXT to `npx.cmd` on Windows, and `spawnPassthrough` spawns it with `shell: true` (required for `.cmd`/`.bat` shims). Node's `shell: true` joins command+args **unquoted** into `cmd.exe /d /s /c "..."`, so `C:\Program Files\nodejs\npx.cmd` splits at the space and cmd executes `C:\Program` → `'C:\Program' is not recognized` → exit 1. Install-path args under `C:\Users\<First Last>\...` break the same way. **Why not `shell: false`.** Spawning a `.cmd` directly with `shell: false` throws `EINVAL` on all current Node versions — the CVE-2024-27980 hardening (Node 18.20.x / 20.12.x / 21.7.x+) forbids spawning batch files without a shell because cmd.exe argument splitting cannot be made injection-safe generically. So the shell path is mandatory for `.cmd` shims, and the args must be quoted for cmd. **Quoting semantics.** `quoteForCmdShell` wraps tokens containing whitespace, quotes, or cmd metacharacters (`^ & | < >`) in double quotes, doubling embedded quotes. Node wraps the joined string in outer quotes after `/d /s /c`; with `/s`, cmd strips only those outer quotes and executes the inner, correctly-quoted line: ``` before: cmd.exe /d /s /c "C:\Program Files\nodejs\npx.cmd --yes skills add C:\Users\First Last\..." after: cmd.exe /d /s /c ""C:\Program Files\nodejs\npx.cmd" --yes skills add "C:\Users\First Last\..."" ``` **Alternative considered.** Resolving `npx-cli.js` next to `npx.cmd` and spawning `process.execPath` with `shell: false` would avoid cmd quoting entirely, but the `npx.cmd` → `npx-cli.js` relative layout differs across npm versions and Node distribution channels (nvm-windows, Volta, Scoop shims, fnm), so it trades a well-understood quoting rule for fragile path archaeology. The quoting approach is smaller and matches what cross-platform tools (e.g. `cross-spawn`) do. **Bounding the installer stages.** - `spawnPassthrough` now enforces a 180s deadline: SIGTERM, then SIGKILL after 5s if the child ignores it. A timed-out install fails with a clear message and a distinct `skill_install_timeout` result code through the existing `fail`/`resultCode` plumbing from #2210. - The catalog file-list fetch, the direct-Blob HEAD probe, and skill-file downloads now use `AbortSignal.timeout(10s)`. An aborted catalog fetch is classified exactly like a network failure (`unavailable`), preserving the existing fallback semantics. - Both deadlines are env-overridable (`BROWSE_SKILLS_INSTALL_TIMEOUT_MS`, `BROWSE_SKILLS_FETCH_TIMEOUT_MS`), following the module's existing `BROWSE_SKILLS_*` override pattern; this is also what makes the deadlines provable end-to-end in tests. ## E2E Test Matrix All commands ran against the locally built CLI (`<local build>/bin/run.js`) on macOS (darwin/arm64). | Command / flow | Observed output | Confidence / sufficiency | | --- | --- | --- | | **Windows execution** (one-time `windows-latest` before/after run: [actions/runs/27448084696](https://github.com/browserbase/stagehand/actions/runs/27448084696)) | System Node at `C:\Program Files\nodejs` (no setup-node), `where npx` → `C:\Program Files\nodejs\npx.cmd`. **main:** `browse skills install` → exit 1, verbatim `'C:\Program' is not recognized as an internal or external command`. **PR head d2e9098:** exit 0, `Installed 1 skill`, `~\.agents\skills\browse\SKILL.md` present; win32 vitest gate (quoting + shim + spawnPassthrough timeout) 10/10 passed. | Closes the Windows gap with a real before/after on the same runner layout (win25-vs2026): the exact predicted failure reproduces on main and the PR build installs end-to-end. Full evidence in the [validation comment](#2250 (comment)). | | `browse skills find flights` (real catalog) | exit 0; returned `google.com/search-flights-ts4g1f` with full metadata | Proves catalog discovery is unaffected. | | `browse skills add google.com/search-flights-ts4g1f` (real catalog, real `npx`) | exit 0; `Downloaded 2 skill files to <config dir>`; `npx skills add` installed the skill ("Installed 1 skill ... Done!") | Proves the darwin install path (quoting branch not taken) still works end-to-end with the new deadline code in place — no regression. | | Quoting before/after for `C:\Program Files\nodejs\npx.cmd` (unit tests + helper output) | before: `C:\Program Files\nodejs\npx.cmd --yes skills add C:\Users\First Last\...` (unquoted → cmd runs `C:\Program`); after: `"C:\Program Files\nodejs\npx.cmd" --yes skills add "C:\Users\First Last\..."` | Reproduces the bug shape and asserts the exact corrected command line, incl. embedded-quote doubling, `& \| ^ < >` metachars, and the empty token. Static proof only — see Windows row. | | Hung `npx` stub (`exec /bin/sleep 600`) + `BROWSE_SKILLS_INSTALL_TIMEOUT_MS=2000` → `browse skills install` | exit 1 after 2s elapsed (timed): `Skill install timed out after 2s waiting for \`npx skills add\`...` | Proves the deadline kills a hung child and surfaces the timeout failure (`skill_install_timeout` flows through the same `fail` plumbing verified in #2210). Also covered by `spawnPassthrough` unit tests (timeout + non-timeout control). | | Hung catalog server (accepts, never responds) at **default timeouts** → `browse skills add google.com/search-flights-ts4g1f` with stubbed `npx` | exit 0 after 21s (10s API fetch abort + 10s Blob HEAD abort); npx stub invoked with `--yes skills add browserbase/browse.sh --skill google.com/search-flights-ts4g1f` | Proves a hung catalog aborts at the 10s default and the catalog-unavailable fallback semantics are preserved. Previously this hung forever. Also covered by a fast CLI-level test with `BROWSE_SKILLS_FETCH_TIMEOUT_MS=500`. | | `npx vitest run` (packages/cli) | 15 files, 224 tests passed (incl. 13 in skills-install.test.ts) | Full CLI suite green; supporting evidence only. | | `pnpm lint` (packages/cli) | exit 0 (prettier + eslint + tsc) | Supporting evidence only. | **Windows gap closed:** a one-time `windows-latest` before/after run ([actions/runs/27448084696](https://github.com/browserbase/stagehand/actions/runs/27448084696), details in the [validation comment](#2250 (comment))) reproduced the exact `'C:\Program' is not recognized` failure on main and verified `browse skills install` succeeds end-to-end on this PR's build with the default `C:\Program Files\nodejs` system Node. Full Windows vitest: 205/224 passed; all 17 failures are pre-existing POSIX test-harness assumptions (`#!/bin/sh` npx stubs etc.), identical by construction on main. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Fixes Windows failures in `browse skills add` by quoting the `npx` command when spawned via cmd.exe and bounding installer/fetch stages to prevent hangs. Also runs the full CLI test suite on Linux and Windows via a matrix job. Addresses Linear: STG-2279. - **Bug Fixes** - Quote command and args when spawning `.cmd`/`.bat` through the shell, so `C:\Program Files\nodejs\npx.cmd` and paths with spaces work. - Add a 180s deadline to `npx skills add` (SIGTERM, then SIGKILL) and a 10s abort for catalog/file fetches; both overridable via `BROWSE_SKILLS_INSTALL_TIMEOUT_MS` and `BROWSE_SKILLS_FETCH_TIMEOUT_MS`; install timeouts surface `skill_install_timeout`. - Run the full CLI suite on `ubuntu-latest` and `windows-latest` via a matrix; POSIX-only tests are guarded via `itPosix`/`describePosix` so Windows gets full coverage without brittle CI filters. <sup>Written for commit 9fe60b7. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2250?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. --> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
felipeofdev-ai
pushed a commit
to felipeofdev-ai/stagehand
that referenced
this pull request
Aug 4, 2026
…il cleanly on unknown skill (browserbase#2210) ## Summary `browse skills add <domain>/<task>` fails for a large share of users with an opaque `EEXIT 1` and no diagnostic detail. Root cause: when the catalog file API returns 404, the CLI silently fell back to git-cloning the **entire** browse.sh repo, found no matching skill, printed "No matching skills found" and exited 1 — and because the child was spawned with `stdio: "inherit"`, the real error text was never captured, so telemetry recorded a bare numeric exit code. This PR makes those failures **measurable** and **friendlier**: - **Capture child output.** `spawnPassthrough` now pipes + echoes the `npx skills add` child's stdout/stderr to the terminal (live UX preserved) while buffering the last ~2KB. On a nonzero exit, that tail flows into the error message and telemetry instead of a bare exit code. - **Fail cleanly on unknown skills.** A definitive 404 for a non-generated id now fails fast with an actionable message (`Skill "<id>" not found in the catalog. Run \`browse skills find <domain>\` ...`) — no more cloning the whole repo. Suffix-shaped (generated) ids and unavailable-catalog cases keep the browse.sh clone fallback (still covered by existing tests). - **Distinct telemetry result codes:** `skill_not_found`, `invalid_skill_id`, `npx_missing`, `skill_install_failed`. - **Better missing-arg UX.** `browse skills add` with no arg now prints guidance pointing at the `<domain>/<task>` form and `browse skills find`, instead of oclif's bare "Missing 1 required arg". `result_code` was already wired through `base.ts` → `recordCommandError` → `commandCompletedProperties`; the gap was that the failing paths never set a meaningful code (they returned a numeric exit that surfaced as a generic oclif usage error). This PR routes each failure through `fail(msg, exitCode, { resultCode })` so the codes are populated. Linear: https://linear.app/browserbase/issue/STG-2192 ## E2E Test Matrix Run against the locally built `browse` (`node packages/cli/bin/run.js`) with a local PostHog capture server (`BROWSERBASE_TELEMETRY_HOST=http://127.0.0.1:<port>`). result_code values are the real captured `cli.command_completed` properties. | Command / flow | Observed output | Confidence / sufficiency | | --- | --- | --- | | `browse skills add amazon.com/buy-something-fake` (hits real browse.sh, 404) | exit 1; stderr: `Skill "amazon.com/buy-something-fake" not found in the catalog. Run \`browse skills find amazon.com\` ...`; telemetry `result_code=skill_not_found`, `error_type=runtime`; **no git clone** | Proves the core fix: clean message + distinct code, no opaque clone. Hits the live API. | | `browse skills add` (no arg) | exit 2; stderr: `Missing skill id. Pass a skill in the form <domain>/<task>, e.g. \`browse skills add yelp.com/extract-reviews\`. Run \`browse skills find <query>\` ...`; telemetry `result_code=invalid_skill_id` | Proves the improved missing-arg UX + code. | | `browse skills add ../bad` | exit 1; stderr: `Invalid skill id "../bad". Use <domain>/<task>.`; telemetry `result_code=invalid_skill_id` | Proves parse-failure code wiring. | | `browse skills add yelp.com/extract-reviews-2ikb22` with stub `npx` exiting 7 | exit 7; live stderr `npx says: simulated registry boom` passed through; `fail()` message `Could not install skill: npx says: simulated registry boom`; telemetry `result_code=skill_install_failed`, `success=false` | Proves child output is captured (not discarded) AND flows into telemetry with the correct code while exit code is preserved. | | `browse skills add yelp.com/extract-reviews-2ikb22` (real npx, isolated `HOME`/`XDG_CONFIG_HOME`) | `Downloaded 1 skill file to <temp>/browserbase/skills/yelp.com/extract-reviews-2ikb22` (SKILL.md present); `npx skills add <localpath>` then failed only due to this machine's unreachable npm registry mirror (`socket-firewall...` ENOTFOUND) — surfaced via the new `skill_install_failed` path | Proves the real browse.sh download stage works end-to-end against the live API; the `npx skills add <localpath>` step is independently covered by the passing vitest stub test. | | `pnpm --filter browse test` (vitest) | 210 passed (15 files), incl. new `skill_not_found` and missing-arg tests | Full suite green, no happy-path regression (download test still asserts `--yes skills add <installPath>`). | | `pnpm --filter browse lint` (prettier + eslint + tsc) | clean | Supporting: format/lint/typecheck pass. | ## Changeset `.changeset/skills-add-failure-telemetry.md` → `"browse": patch` (consumed by the dedicated `release-cli.yml` CLI release flow, which scans for `"browse"` changesets). 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Improves `browse skills add <domain>/<task>` with clear, actionable errors and measurable telemetry, and stops cloning the entire repo on unknown skills. Addresses Linear STG-2192. - **Bug Fixes** - Streams and buffers the last ~2KB of `npx skills add` output; on nonzero exit or spawn errors, includes the tail/OS error in the message and telemetry as `skill_install_failed`. - Treats catalog 404s for non-generated IDs as a clean failure with guidance (e.g., use `browse skills find <domain>`), avoiding cloning `browserbase/browse.sh`. - Keeps GitHub fallback for suffix-shaped IDs or when the catalog API is unavailable. - Adds distinct telemetry result codes: `skill_not_found`, `invalid_skill_id`, `npx_missing`, `skill_install_failed`. - Replaces the generic missing-arg error with guidance on `<domain>/<task>` and `browse skills find`. <sup>Written for commit 84d9040. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2210?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> ---------
felipeofdev-ai
pushed a commit
to felipeofdev-ai/stagehand
that referenced
this pull request
Aug 4, 2026
…-failure backoff (browserbase#2248) ## Summary Makes browse driver (browser session) failures actionable, classified, and self-correcting. Today an invalid `BROWSERBASE_API_KEY` surfaces a bare `Error: 401 Unauthorized` with no remediation, the 5s init-failure cache makes every retry instant and identical, and most driver failures reach telemetry as `unexpected`. Linear: [STG-2277](https://linear.app/browserbase/issue/STG-2277/make-browse-driver-errors-actionable-with-result-codes-and-init) ## Impact if merged This targets the browse CLI's largest failure mode by volume and by user pain. 71 installs stuck in get/screenshot retry loops generate 92.3% of ALL CLI telemetry (~5.5M events/30d); ~375k of those events come from tagged claude-code and codex agents on current versions — exactly the target ICP (coding agents driving browsers). Root cause (smoke-tested): any `BROWSERBASE_API_KEY` forces remote mode; an invalid key surfaces a bare `Error: 401 Unauthorized` with no remediation, and the 5s init-failure cache makes every retry instant, so agents can't self-correct and loop forever. Separately, 2,337 distinct users hit missing_api_key/auth_401 in 30d, and `open` — whose failures are 94% unclassifiable today (result_code `unexpected`) — gates activation: only 28.5% of real users reach an activated session, and a failed first command cuts 7-day retention 12.4x. This PR makes auth/driver failures actionable (agents recover in one turn) and classified (we can finally measure why open fails). ## Implementation notes - **Remote init classification** (`remote.ts`): new `classifyRemoteInitError()` duck-types the SDK error's `status` — 401 → `remote_auth_401` (invalid-key message with settings link, `--local`, `browse doctor`), 403 → `remote_auth_403` (permissions/plan wording, same escape hatches), other → `remote_session_create_failed` (original message preserved + `browse doctor` pointer). Wired through the `RemoteCapability` interface so local-only builds compile. - **Chrome-not-found** (`session-manager.ts`): chrome-launcher's `ERR_LAUNCHER_NOT_INSTALLED` / `ERR_LAUNCHER_PATH_NOT_SET` failures in managed-local mode get install/`--cdp`/remote guidance instead of leaking launcher internals. - **Init-failure backoff**: cached init failures now back off exponentially — `min(5s * 2^(n-1), 5min)` — reset on success and `close()`. After ≥3 consecutive failures the cached message gains a `(failing repeatedly — fix BROWSERBASE_API_KEY, use --local, or run browse doctor)` suffix (deduped on rethrow). - **Result codes over the daemon protocol**: `ErrorResponseSchema` gains optional `code`/`httpStatus` (backward compatible — old daemons omit them); the daemon's `formatError` surfaces them from typed `DriverError`s; the client rethrows as `CommandFailure` with `resultCode`/`httpStatus` so the existing browserbase#2210 telemetry plumbing records them. Client-side fail sites tagged: `daemon_lock_timeout`, `daemon_unresponsive`, `daemon_socket_timeout`, `daemon_spawn_failed`. Already-authored driver errors tagged: `stale_ref` (unknown ref), `no_active_page`. - **Local-only build contract preserved**: remediation strings that mention `BROWSERBASE_API_KEY` live behind the remote capability (`driverInitHints()`), so the `build:local-only` artifact stays key-free (guarded by the existing `local-only-build.test.ts`, which caught the first draft). ## E2E Test Matrix | Command / flow | Observed output | Confidence / sufficiency | | --- | --- | --- | | `BROWSERBASE_API_KEY=bb_invalid_test <local build> get url` | `Browserbase rejected your BROWSERBASE_API_KEY (401 Unauthorized). A set key makes browse default to remote mode. Check the key at https://browserbase.com/settings, run without one using --local (browse open <url> --local), or diagnose with browse doctor.` exit=1 | Proves the new 401 classification flows daemon → protocol → client → stderr end-to-end against the real Browserbase API. | | Same command 4x rapidly (cached failure window) | Identical actionable message each time, ~400ms per run (no remote round-trip) | Proves cached failures keep the actionable message and stay instant; does not by itself prove backoff growth. | | Same command after 6s, then after 11s more (real failures browserbase#2, browserbase#3) | Message gains ` (failing repeatedly — fix BROWSERBASE_API_KEY, use --local, or run browse doctor)` suffix, exactly once, exit=1 | Proves the ≥3-consecutive-failures hint and suffix dedupe on the live failure path. | | Valid key: `open https://example.com` → `get title` → `stop` | `"mode": "remote" ... "title": "Example Domain"`, then `{"title": "Example Domain"}`, then `{"stopped": true}` | Proves the remote happy path is unchanged (no regression in outputs or exit codes). | | `env -u BROWSERBASE_API_KEY <local build> open https://example.com --local` → `get url` | `"mode": "managed-local" ... "url": "https://example.com/"`, then `{"url": "https://example.com/"}` exit=0 | Proves keyless managed-local mode is unaffected. | | `get text @9-99` on the local session | `Unknown ref "9-99" - run browse snapshot first to populate refs (have 0 refs).` exit=1 | Proves the stale-ref message is unchanged while now carrying `stale_ref` through the protocol (round-trip unit-tested). | | `browse doctor` with and without key | `Status: ok` in both; `target remote` with key, `target managed-local` without | Proves doctor behavior unchanged. | | `pnpm build` + `pnpm lint` (prettier, eslint, tsc) | All pass | Supporting only. | | `pnpm test:cli` | 16 files / 228 tests pass, incl. new `driver-errors.test.ts` (classification, backoff schedule, chrome-not-found detection, protocol round-trip, key-free local-only hints) and the `local-only-build` artifact guard | Supporting; covers mappings and the local-only security contract not exercised by live smokes. | 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Makes browse driver failures actionable and self-correcting with classified result codes and exponential init backoff. Addresses Linear STG-2277 by giving clear fixes for bad `BROWSERBASE_API_KEY`, missing Chrome/Chromium, and daemon issues, while improving telemetry. - **New Features** - Classify remote init errors into actionable messages with codes: `remote_auth_401`, `remote_auth_403`, `remote_session_create_failed` (with links to settings, `--local`, and `browse doctor`). - Add error result codes to the daemon protocol (`code`, `httpStatus`) and propagate to the client for telemetry. - Exponential backoff for cached init failures (5s doubling, capped at 1 minute) with a “failing repeatedly” hint after 3 failures. - Tag common failures with stable codes: `daemon_lock_timeout`, `daemon_unresponsive`, `daemon_socket_timeout`, `daemon_spawn_failed`, `stale_ref`, `no_active_page`, `no_chrome_found`. - Use `http-status-codes` for status mapping and extract chrome-launcher error codes to a constant (no behavior change). - **Bug Fixes** - Chrome-not-found now gives Chromium-first guidance: Linux `apt install chromium`; macOS `brew install --cask google-chrome` or set `CHROME_PATH` for Chromium, plus `--cdp` or remote as options. - Keep the local-only build key-free by moving `BROWSERBASE_API_KEY` remediation strings behind the remote capability. <sup>Written for commit b7a3f7e. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2248?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. --> ---------
felipeofdev-ai
pushed a commit
to felipeofdev-ai/stagehand
that referenced
this pull request
Aug 4, 2026
…tages (browserbase#2250) ## Summary Linear: https://linear.app/browserbase/issue/STG-2279/fix-windows-skills-add-npx-quoting-and-bound-installer-timeouts Fixes `browse skills add` on Windows (cmd.exe spawn quoting) and bounds the two unbounded skills-installer stages (the `npx skills add` child and the catalog/file fetches). ## Impact if merged skills.add succeeds for 3.2% of Windows users (54 in the last 30d) — effectively broken for every default `C:\Program Files\nodejs` Node install, because the npx child is spawned through cmd.exe with an unquoted path. Windows devs are a smaller slice of the CLI base, but skill installers are the single highest-value cohort in our telemetry: 7x the engagement (median 12.5 vs 2 commands) and 19x the multi-day retention (28.4% vs 1.5%) of non-installers, and skills.find→add is an agent-facing funnel (51% of finders attempt an install within an hour). This also bounds the two unbounded installer stages (npx child: 180s; catalog fetches: 10s) — today they can hang forever, feeding the slow-failure retry loops that dominate telemetry volume. ## Implementation notes **Root cause.** `findExecutable` resolves `npx` via PATH+PATHEXT to `npx.cmd` on Windows, and `spawnPassthrough` spawns it with `shell: true` (required for `.cmd`/`.bat` shims). Node's `shell: true` joins command+args **unquoted** into `cmd.exe /d /s /c "..."`, so `C:\Program Files\nodejs\npx.cmd` splits at the space and cmd executes `C:\Program` → `'C:\Program' is not recognized` → exit 1. Install-path args under `C:\Users\<First Last>\...` break the same way. **Why not `shell: false`.** Spawning a `.cmd` directly with `shell: false` throws `EINVAL` on all current Node versions — the CVE-2024-27980 hardening (Node 18.20.x / 20.12.x / 21.7.x+) forbids spawning batch files without a shell because cmd.exe argument splitting cannot be made injection-safe generically. So the shell path is mandatory for `.cmd` shims, and the args must be quoted for cmd. **Quoting semantics.** `quoteForCmdShell` wraps tokens containing whitespace, quotes, or cmd metacharacters (`^ & | < >`) in double quotes, doubling embedded quotes. Node wraps the joined string in outer quotes after `/d /s /c`; with `/s`, cmd strips only those outer quotes and executes the inner, correctly-quoted line: ``` before: cmd.exe /d /s /c "C:\Program Files\nodejs\npx.cmd --yes skills add C:\Users\First Last\..." after: cmd.exe /d /s /c ""C:\Program Files\nodejs\npx.cmd" --yes skills add "C:\Users\First Last\..."" ``` **Alternative considered.** Resolving `npx-cli.js` next to `npx.cmd` and spawning `process.execPath` with `shell: false` would avoid cmd quoting entirely, but the `npx.cmd` → `npx-cli.js` relative layout differs across npm versions and Node distribution channels (nvm-windows, Volta, Scoop shims, fnm), so it trades a well-understood quoting rule for fragile path archaeology. The quoting approach is smaller and matches what cross-platform tools (e.g. `cross-spawn`) do. **Bounding the installer stages.** - `spawnPassthrough` now enforces a 180s deadline: SIGTERM, then SIGKILL after 5s if the child ignores it. A timed-out install fails with a clear message and a distinct `skill_install_timeout` result code through the existing `fail`/`resultCode` plumbing from browserbase#2210. - The catalog file-list fetch, the direct-Blob HEAD probe, and skill-file downloads now use `AbortSignal.timeout(10s)`. An aborted catalog fetch is classified exactly like a network failure (`unavailable`), preserving the existing fallback semantics. - Both deadlines are env-overridable (`BROWSE_SKILLS_INSTALL_TIMEOUT_MS`, `BROWSE_SKILLS_FETCH_TIMEOUT_MS`), following the module's existing `BROWSE_SKILLS_*` override pattern; this is also what makes the deadlines provable end-to-end in tests. ## E2E Test Matrix All commands ran against the locally built CLI (`<local build>/bin/run.js`) on macOS (darwin/arm64). | Command / flow | Observed output | Confidence / sufficiency | | --- | --- | --- | | **Windows execution** (one-time `windows-latest` before/after run: [actions/runs/27448084696](https://github.com/browserbase/stagehand/actions/runs/27448084696)) | System Node at `C:\Program Files\nodejs` (no setup-node), `where npx` → `C:\Program Files\nodejs\npx.cmd`. **main:** `browse skills install` → exit 1, verbatim `'C:\Program' is not recognized as an internal or external command`. **PR head d2e9098:** exit 0, `Installed 1 skill`, `~\.agents\skills\browse\SKILL.md` present; win32 vitest gate (quoting + shim + spawnPassthrough timeout) 10/10 passed. | Closes the Windows gap with a real before/after on the same runner layout (win25-vs2026): the exact predicted failure reproduces on main and the PR build installs end-to-end. Full evidence in the [validation comment](browserbase#2250 (comment)). | | `browse skills find flights` (real catalog) | exit 0; returned `google.com/search-flights-ts4g1f` with full metadata | Proves catalog discovery is unaffected. | | `browse skills add google.com/search-flights-ts4g1f` (real catalog, real `npx`) | exit 0; `Downloaded 2 skill files to <config dir>`; `npx skills add` installed the skill ("Installed 1 skill ... Done!") | Proves the darwin install path (quoting branch not taken) still works end-to-end with the new deadline code in place — no regression. | | Quoting before/after for `C:\Program Files\nodejs\npx.cmd` (unit tests + helper output) | before: `C:\Program Files\nodejs\npx.cmd --yes skills add C:\Users\First Last\...` (unquoted → cmd runs `C:\Program`); after: `"C:\Program Files\nodejs\npx.cmd" --yes skills add "C:\Users\First Last\..."` | Reproduces the bug shape and asserts the exact corrected command line, incl. embedded-quote doubling, `& \| ^ < >` metachars, and the empty token. Static proof only — see Windows row. | | Hung `npx` stub (`exec /bin/sleep 600`) + `BROWSE_SKILLS_INSTALL_TIMEOUT_MS=2000` → `browse skills install` | exit 1 after 2s elapsed (timed): `Skill install timed out after 2s waiting for \`npx skills add\`...` | Proves the deadline kills a hung child and surfaces the timeout failure (`skill_install_timeout` flows through the same `fail` plumbing verified in browserbase#2210). Also covered by `spawnPassthrough` unit tests (timeout + non-timeout control). | | Hung catalog server (accepts, never responds) at **default timeouts** → `browse skills add google.com/search-flights-ts4g1f` with stubbed `npx` | exit 0 after 21s (10s API fetch abort + 10s Blob HEAD abort); npx stub invoked with `--yes skills add browserbase/browse.sh --skill google.com/search-flights-ts4g1f` | Proves a hung catalog aborts at the 10s default and the catalog-unavailable fallback semantics are preserved. Previously this hung forever. Also covered by a fast CLI-level test with `BROWSE_SKILLS_FETCH_TIMEOUT_MS=500`. | | `npx vitest run` (packages/cli) | 15 files, 224 tests passed (incl. 13 in skills-install.test.ts) | Full CLI suite green; supporting evidence only. | | `pnpm lint` (packages/cli) | exit 0 (prettier + eslint + tsc) | Supporting evidence only. | **Windows gap closed:** a one-time `windows-latest` before/after run ([actions/runs/27448084696](https://github.com/browserbase/stagehand/actions/runs/27448084696), details in the [validation comment](browserbase#2250 (comment))) reproduced the exact `'C:\Program' is not recognized` failure on main and verified `browse skills install` succeeds end-to-end on this PR's build with the default `C:\Program Files\nodejs` system Node. Full Windows vitest: 205/224 passed; all 17 failures are pre-existing POSIX test-harness assumptions (`#!/bin/sh` npx stubs etc.), identical by construction on main. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Fixes Windows failures in `browse skills add` by quoting the `npx` command when spawned via cmd.exe and bounding installer/fetch stages to prevent hangs. Also runs the full CLI test suite on Linux and Windows via a matrix job. Addresses Linear: STG-2279. - **Bug Fixes** - Quote command and args when spawning `.cmd`/`.bat` through the shell, so `C:\Program Files\nodejs\npx.cmd` and paths with spaces work. - Add a 180s deadline to `npx skills add` (SIGTERM, then SIGKILL) and a 10s abort for catalog/file fetches; both overridable via `BROWSE_SKILLS_INSTALL_TIMEOUT_MS` and `BROWSE_SKILLS_FETCH_TIMEOUT_MS`; install timeouts surface `skill_install_timeout`. - Run the full CLI suite on `ubuntu-latest` and `windows-latest` via a matrix; POSIX-only tests are guarded via `itPosix`/`describePosix` so Windows gets full coverage without brittle CI filters. <sup>Written for commit 9fe60b7. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2250?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. --> ---------
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
browse skills add <domain>/<task>fails for a large share of users with an opaqueEEXIT 1and no diagnostic detail. Root cause: when the catalog file API returns 404, the CLI silently fell back to git-cloning the entire browse.sh repo, found no matching skill, printed "No matching skills found" and exited 1 — and because the child was spawned withstdio: "inherit", the real error text was never captured, so telemetry recorded a bare numeric exit code.This PR makes those failures measurable and friendlier:
spawnPassthroughnow pipes + echoes thenpx skills addchild's stdout/stderr to the terminal (live UX preserved) while buffering the last ~2KB. On a nonzero exit, that tail flows into the error message and telemetry instead of a bare exit code.Skill "<id>" not found in the catalog. Run \browse skills find ` ...`) — no more cloning the whole repo. Suffix-shaped (generated) ids and unavailable-catalog cases keep the browse.sh clone fallback (still covered by existing tests).skill_not_found,invalid_skill_id,npx_missing,skill_install_failed.browse skills addwith no arg now prints guidance pointing at the<domain>/<task>form andbrowse skills find, instead of oclif's bare "Missing 1 required arg".result_codewas already wired throughbase.ts→recordCommandError→commandCompletedProperties; the gap was that the failing paths never set a meaningful code (they returned a numeric exit that surfaced as a generic oclif usage error). This PR routes each failure throughfail(msg, exitCode, { resultCode })so the codes are populated.Linear: https://linear.app/browserbase/issue/STG-2192
E2E Test Matrix
Run against the locally built
browse(node packages/cli/bin/run.js) with a local PostHog capture server (BROWSERBASE_TELEMETRY_HOST=http://127.0.0.1:<port>). result_code values are the real capturedcli.command_completedproperties.browse skills add amazon.com/buy-something-fake(hits real browse.sh, 404)Skill "amazon.com/buy-something-fake" not found in the catalog. Run \browse skills find amazon.com` ...; telemetryresult_code=skill_not_found,error_type=runtime`; no git clonebrowse skills add(no arg)Missing skill id. Pass a skill in the form <domain>/<task>, e.g. \browse skills add yelp.com/extract-reviews`. Run `browse skills find ` ...; telemetryresult_code=invalid_skill_id`browse skills add ../badInvalid skill id "../bad". Use <domain>/<task>.; telemetryresult_code=invalid_skill_idbrowse skills add yelp.com/extract-reviews-2ikb22with stubnpxexiting 7npx says: simulated registry boompassed through;fail()messageCould not install skill: npx says: simulated registry boom; telemetryresult_code=skill_install_failed,success=falsebrowse skills add yelp.com/extract-reviews-2ikb22(real npx, isolatedHOME/XDG_CONFIG_HOME)Downloaded 1 skill file to <temp>/browserbase/skills/yelp.com/extract-reviews-2ikb22(SKILL.md present);npx skills add <localpath>then failed only due to this machine's unreachable npm registry mirror (socket-firewall...ENOTFOUND) — surfaced via the newskill_install_failedpathnpx skills add <localpath>step is independently covered by the passing vitest stub test.pnpm --filter browse test(vitest)skill_not_foundand missing-arg tests--yes skills add <installPath>).pnpm --filter browse lint(prettier + eslint + tsc)Changeset
.changeset/skills-add-failure-telemetry.md→"browse": patch(consumed by the dedicatedrelease-cli.ymlCLI release flow, which scans for"browse"changesets).🤖 Generated with Claude Code
Summary by cubic
Improves
browse skills add <domain>/<task>with clear, actionable errors and measurable telemetry, and stops cloning the entire repo on unknown skills. Addresses Linear STG-2192.npx skills addoutput; on nonzero exit or spawn errors, includes the tail/OS error in the message and telemetry asskill_install_failed.browse skills find <domain>), avoiding cloningbrowserbase/browse.sh.skill_not_found,invalid_skill_id,npx_missing,skill_install_failed.<domain>/<task>andbrowse skills find.Written for commit 84d9040. Summary will update on new commits.