[fix]: fail fast on .connect() when no ext found - #2790
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
🦋 Changeset detectedLatest commit: 6739a50 The changes in this PR will be included in the next version bump. This PR includes changesets to release 10 packages
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 |
There was a problem hiding this comment.
4 issues found across 11 files
Confidence score: 3/5
- In
packages/sdk-python/src/stagehand/cdp_client.py, extension discovery can accept an enabled runtime when another matching installation is disabled, allowing ambiguous multiple installations and potentially connecting to the wrong extension; count all matching installed extensions before returning an ID. - The discovery error paths in
packages/sdk-ts/src/cdpClient.ts(discoverInstalledStagehandExtensionIdandCDPClient.connect) andpackages/sdk-python/src/stagehand/cdp_client.pyraise genericError/RuntimeErrorvalues, making failures harder to classify and potentially exposing unsanitized messages; use the dedicated typed configuration/discovery errors instead.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/sdk-python/src/stagehand/cdp_client.py">
<violation number="1" location="packages/sdk-python/src/stagehand/cdp_client.py:414">
P2: When the browser contains one enabled and another disabled `Stagehand Runtime`, this branch accepts the enabled ID instead of rejecting multiple installations. Check the total matching `installed` count before returning an ID so `.connect()` honors the fail-on-duplicate contract.</violation>
<violation number="2" location="packages/sdk-python/src/stagehand/cdp_client.py:418">
P2: Custom agent: **Exception and error message sanitization**
The new extension-discovery failure path raises generic `RuntimeError` instead of a dedicated typed error, contrary to the exception sanitization rule. Define and use a specific Stagehand CDP/extension error class for the discovery and inventory-validation failures.</violation>
</file>
<file name="packages/sdk-ts/src/cdpClient.ts">
<violation number="1" location="packages/sdk-ts/src/cdpClient.ts:238">
P2: Custom agent: **Exception and error message sanitization**
When `CDPClient.connect` receives no extension source, it propagates a generic `Error`. Throw a dedicated typed configuration error instead.</violation>
<violation number="2" location="packages/sdk-ts/src/cdpClient.ts:528">
P1: Custom agent: **Exception and error message sanitization**
When `.connect()` discovers an invalid Stagehand extension inventory, `discoverInstalledStagehandExtensionId` rethrows generic `Error` instances to the caller. Replace these with a typed discovery error using fixed sanitized messages, and do not include extension IDs.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Client as Stagehand Client
participant CDP as CDP Client
participant Chrome as Chrome Browser
participant Ext as Extensions API
participant SW as Service Worker
Note over Client,SW: Connect to Browserbase Session
Client->>CDP: connect({ preloadedExtension: true })
CDP->>Chrome: open CDP WebSocket
Note over CDP,Ext: NEW: Discover Stagehand Extension
CDP->>Ext: Extensions.getExtensions()
Ext-->>CDP: extension list (id, name, enabled)
alt Extension not found
CDP-->>Client: Error: "Stagehand extension is not installed"
else Extension disabled
CDP-->>Client: Error: "installed but disabled"
else Multiple enabled extensions
CDP-->>Client: Error: "Multiple enabled Stagehand extensions installed"
else Single enabled extension found
CDP->>CDP: Extract extensionId from inventory
end
Note over CDP,SW: Attach to Service Worker
CDP->>Chrome: Target.getTargets()
Chrome-->>CDP: target list
CDP->>CDP: Filter for chrome-extension://{extensionId}/
alt Worker not found
CDP->>Chrome: Target.createTarget (activate worker)
Chrome-->>CDP: activation targetId
end
CDP->>SW: Target.attachToTarget({ targetId, flatten: true })
SW-->>CDP: sessionId
CDP->>SW: Runtime.enable
CDP->>SW: Runtime.addBinding
CDP->>SW: Runtime.evaluate (readiness check)
SW-->>CDP: runtime marker + hasReceiver
CDP-->>Client: CDPClient (with sessionId, extensionId)
Client->>Client: Store serviceWorker info
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| .map((extension) => extension.id) | ||
| .sort() | ||
| .join(", "); | ||
| throw new Error(`Multiple enabled Stagehand extensions are installed: ${ids}`); |
There was a problem hiding this comment.
P1: Custom agent: Exception and error message sanitization
When .connect() discovers an invalid Stagehand extension inventory, discoverInstalledStagehandExtensionId rethrows generic Error instances to the caller. Replace these with a typed discovery error using fixed sanitized messages, and do not include extension IDs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/sdk-ts/src/cdpClient.ts, line 528:
<comment>When `.connect()` discovers an invalid Stagehand extension inventory, `discoverInstalledStagehandExtensionId` rethrows generic `Error` instances to the caller. Replace these with a typed discovery error using fixed sanitized messages, and do not include extension IDs.</comment>
<file context>
@@ -496,82 +503,37 @@ export async function waitForRuntimeReady(
+ .map((extension) => extension.id)
+ .sort()
+ .join(", ");
+ throw new Error(`Multiple enabled Stagehand extensions are installed: ${ids}`);
+ }
+ if (installed.length > 0) {
</file context>
| if len(enabled) == 1: | ||
| return enabled[0].id | ||
| if len(enabled) > 1: | ||
| ids = ", ".join(sorted(extension.id for extension in enabled)) | ||
| raise RuntimeError(f"Multiple enabled Stagehand extensions are installed: {ids}") | ||
| if installed: | ||
| raise RuntimeError( | ||
| "Stagehand extension is installed in the connected browser but is disabled." | ||
| ) |
There was a problem hiding this comment.
P2: When the browser contains one enabled and another disabled Stagehand Runtime, this branch accepts the enabled ID instead of rejecting multiple installations. Check the total matching installed count before returning an ID so .connect() honors the fail-on-duplicate contract.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/sdk-python/src/stagehand/cdp_client.py, line 414:
<comment>When the browser contains one enabled and another disabled `Stagehand Runtime`, this branch accepts the enabled ID instead of rejecting multiple installations. Check the total matching `installed` count before returning an ID so `.connect()` honors the fail-on-duplicate contract.</comment>
<file context>
@@ -394,9 +402,32 @@ async def _load_unpacked_extension(self, extension_dir: str) -> str:
+ ]
+ enabled = [extension for extension in installed if extension.enabled]
+
+ if len(enabled) == 1:
+ return enabled[0].id
+ if len(enabled) > 1:
</file context>
| if len(enabled) == 1: | |
| return enabled[0].id | |
| if len(enabled) > 1: | |
| ids = ", ".join(sorted(extension.id for extension in enabled)) | |
| raise RuntimeError(f"Multiple enabled Stagehand extensions are installed: {ids}") | |
| if installed: | |
| raise RuntimeError( | |
| "Stagehand extension is installed in the connected browser but is disabled." | |
| ) | |
| if len(installed) > 1: | |
| ids = ", ".join(sorted(extension.id for extension in installed)) | |
| raise RuntimeError(f"Multiple Stagehand extensions are installed: {ids}") | |
| if len(enabled) == 1: | |
| return enabled[0].id | |
| if installed: | |
| raise RuntimeError( | |
| "Stagehand extension is installed in the connected browser but is disabled." | |
| ) |
| return enabled[0].id | ||
| if len(enabled) > 1: | ||
| ids = ", ".join(sorted(extension.id for extension in enabled)) | ||
| raise RuntimeError(f"Multiple enabled Stagehand extensions are installed: {ids}") |
There was a problem hiding this comment.
P2: Custom agent: Exception and error message sanitization
The new extension-discovery failure path raises generic RuntimeError instead of a dedicated typed error, contrary to the exception sanitization rule. Define and use a specific Stagehand CDP/extension error class for the discovery and inventory-validation failures.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/sdk-python/src/stagehand/cdp_client.py, line 418:
<comment>The new extension-discovery failure path raises generic `RuntimeError` instead of a dedicated typed error, contrary to the exception sanitization rule. Define and use a specific Stagehand CDP/extension error class for the discovery and inventory-validation failures.</comment>
<file context>
@@ -394,9 +402,32 @@ async def _load_unpacked_extension(self, extension_dir: str) -> str:
+ return enabled[0].id
+ if len(enabled) > 1:
+ ids = ", ".join(sorted(extension.id for extension in enabled))
+ raise RuntimeError(f"Multiple enabled Stagehand extensions are installed: {ids}")
+ if installed:
+ raise RuntimeError(
</file context>
| }, | ||
| undefined, | ||
| signal, | ||
| throw new Error( |
There was a problem hiding this comment.
P2: Custom agent: Exception and error message sanitization
When CDPClient.connect receives no extension source, it propagates a generic Error. Throw a dedicated typed configuration error instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/sdk-ts/src/cdpClient.ts, line 238:
<comment>When `CDPClient.connect` receives no extension source, it propagates a generic `Error`. Throw a dedicated typed configuration error instead.</comment>
<file context>
@@ -213,39 +227,32 @@ export class CDPClient {
- },
- undefined,
- signal,
+ throw new Error(
+ "Exactly one of extensionDir, extensionId, or preloadedExtension is required",
);
</file context>
| return { | ||
| id, | ||
| name, | ||
| version: "1.0.0", |
There was a problem hiding this comment.
I believe we published a patch so might be 1.0.1 for the extension, might need to update this test to pull from STAGEHAND_RUNTIME_VERSION
why
what changed
Extensions.getExtensionsStagehand Runtime.connect()docs so externally managed sessions include a previously uploaded Stagehand extension when they are createdbrowserbase.connect()by session IDtest plan
Summary by cubic
Fail fast when connecting to a Browserbase session that lacks the Stagehand extension. Previously
.connect()hung for ~60s and timed out; now it errors immediately with a clear message when the extension is missing, disabled, or duplicated.Extensions.getExtensionsto find the installed and enabled “Stagehand Runtime,” then attaches to its service worker.browserbase.launch()behavior unchanged (it uploads and configures the extension automatically).browserbase.connect().Review notes
extensionIdwhen creating sessions and usebrowserbase.connect()for reconnection;localBrowser.connect()examples target local CDP only.Rollout / migration
extensionIdto the Browserbase Sessions API.browserbase.connect({ sessionId }). Do not rely onlocalBrowser.connect()for Browserbase sessions..connect()if the extension is missing, disabled, or installed more than once.Written for commit 6739a50. Summary will update on new commits.