fix(browser): isolate timed-out snippets#118
Conversation
There was a problem hiding this comment.
1 issue found across 4 files
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/bcode-browser/src/browser-execute.ts">
<violation number="1" location="packages/bcode-browser/src/browser-execute.ts:235">
P2: Timed-out snippets awaiting `session.waitFor()` remain pending until their own timeout (30s by default), rather than failing when the tool expires. `invalidate()` clears their listener without rejecting the promise; track and reject these waiters (and reject new `waitFor` calls after invalidation) with the reset error.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| orElse: () => | ||
| Effect.gen(function* () { | ||
| const error = new Error("browser_execute timed out; CDP session was reset") | ||
| yield* Effect.sync(() => SessionStore.invalidate(ctx.sessionID, session, error)) |
There was a problem hiding this comment.
P2: Timed-out snippets awaiting session.waitFor() remain pending until their own timeout (30s by default), rather than failing when the tool expires. invalidate() clears their listener without rejecting the promise; track and reject these waiters (and reject new waitFor calls after invalidation) with the reset error.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/src/browser-execute.ts, line 235:
<comment>Timed-out snippets awaiting `session.waitFor()` remain pending until their own timeout (30s by default), rather than failing when the tool expires. `invalidate()` clears their listener without rejecting the promise; track and reject these waiters (and reject new `waitFor` calls after invalidation) with the reset error.</comment>
<file context>
@@ -228,9 +229,15 @@ export const make = Effect.fn("BrowserExecute.make")(function* (dataDir: string)
+ orElse: () =>
+ Effect.gen(function* () {
+ const error = new Error("browser_execute timed out; CDP session was reset")
+ yield* Effect.sync(() => SessionStore.invalidate(ctx.sessionID, session, error))
+ return yield* Effect.fail(error)
+ }),
</file context>
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
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/bcode-browser/src/cdp/session.ts">
<violation number="1" location="packages/bcode-browser/src/cdp/session.ts:89">
P2: A timed-out snippet using `{ profileDir }` can leave its `connect()` Promise polling for the entire profile-resolution timeout because invalidation is checked only after `resolveWsUrl()` returns. Making profile resolution cancellation-aware, or racing it with an invalidation signal, would release the abandoned operation immediately while retaining the post-resolution guard against a late socket.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
| * and we connect directly to the supplied endpoint. | ||
| */ | ||
| async connect(opts: ConnectOptions = {}): Promise<void> { | ||
| this.throwIfInvalidated(); |
There was a problem hiding this comment.
P2: A timed-out snippet using { profileDir } can leave its connect() Promise polling for the entire profile-resolution timeout because invalidation is checked only after resolveWsUrl() returns. Making profile resolution cancellation-aware, or racing it with an invalidation signal, would release the abandoned operation immediately while retaining the post-resolution guard against a late socket.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/src/cdp/session.ts, line 89:
<comment>A timed-out snippet using `{ profileDir }` can leave its `connect()` Promise polling for the entire profile-resolution timeout because invalidation is checked only after `resolveWsUrl()` returns. Making profile resolution cancellation-aware, or racing it with an invalidation signal, would release the abandoned operation immediately while retaining the post-resolution guard against a late socket.</comment>
<file context>
@@ -86,7 +86,7 @@ export class Session implements Transport {
*/
async connect(opts: ConnectOptions = {}): Promise<void> {
- if (this.invalidatedError) throw this.invalidatedError;
+ this.throwIfInvalidated();
// No-argument connect is an ensure-connected operation. Reopening the
</file context>
Problem
browser_executeruns snippets in-process. JavaScript Promises cannot be preempted, so timing out the surrounding tool does not stop a yielding snippet. Its abandoned Promise can wake later, reconnect, and send CDP commands after the caller has already received a timeout.Closing the socket alone is insufficient because the same Session object can call
connect()again.Change
SessionStoreso the next tool call receives a fresh Session.Reproduction
The regression test uses a local synthetic CDP WebSocket:
Runtime.evaluate.Validation
bun test test/browser-execute.test.ts: 6 pass, 6 environment-gated skipsbun typecheckinpackages/bcode-browseredit + cache-bustworkspace-import failure on Bun 1.3.14Summary by cubic
Prevents timed-out
browser_executesnippets from reconnecting or sending late CDP commands by permanently invalidating theirSession. Also stops any in-flight or future connection attempts after invalidation; the next call gets a freshSession.Session.invalidate(error)to retire aSession: reject pending/future transport ops (connect,send,waitFor), close active and still-connecting sockets, clear listeners/state, gateisConnected, and guardconnect/openWsacross wsUrl/profile resolution and browser detection.SessionStore.invalidate(sessionID, expected, error)to remove the exact timed-outSessionby identity;SessionStore.getnow returns a newSessionafter timeouts.browser_executenow invalidates the activeSessionon timeout and returns a clearer error: "browser_execute timed out; CDP session was reset".Written for commit f7bd611. Summary will update on new commits.