Skip to content

fix(browser): preserve session after tool timeout - #136

Merged
MagMueller merged 9 commits into
mainfrom
timeout-recovery
Aug 3, 2026
Merged

fix(browser): preserve session after tool timeout#136
MagMueller merged 9 commits into
mainfrom
timeout-recovery

Conversation

@MagMueller

@MagMueller MagMueller commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • keep the underlying CDP connection and target attachment alive when browser_execute times out
  • scope each snippet so its orphaned timers, event callbacks, and result callbacks cannot issue later browser commands
  • let the next tool call continue with the same session, tabs, and active target
  • update the existing timeout rule and add one concise fact about new tabs being separate page targets
  • leave ordinary socket-disconnect behavior unchanged

Why

JavaScript promises cannot be preemptively cancelled. Retiring the complete CDP transport prevents late commands, but also destroys browser context. A per-call async execution scope blocks future browser effects from the timed-out invocation without reconnecting or re-enumerating targets.

The required browser skill changes only two narrow cases: after a timeout it says the existing session is preserved, and when a new tab opens it explains that the active attachment remains on the old page until the intended type: "page" target is selected. Other navigation and waiting guidance is unchanged.

Testing

  • targeted browser timeout/session regressions: 25 passed, 6 environment-dependent skipped
  • exact in-flight CDP regression: the delayed command resolves after the tool deadline, its orphaned follow-up is rejected, and the next invocation succeeds over the same socket and target
  • bun run typecheck in packages/bcode-browser
  • repository pre-push typecheck (17 packages)
  • GitHub typecheck, security scan, and automated review
  • isolated live-browser tasks completed successfully after real page-target stalls; the agent retained control and chose whether to probe or attach a fresh target

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 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:312">
P2: When a timeout recovery cannot find the preserved page target (and there is no fallback page target either), this branch throws and leaves `v4TimeoutRecovery[sessionID]` set. On every later `browser_execute` for the same session, `ensureCloudConnected` re-enters the recovery path (`v4Bootstrapped.has(sessionID) && !recoveryState` is false), skips the already-open connect, hits `getTargets`, finds no page, and throws the same error again — and because `ensureCloudConnected` runs before the snippet is executed, the agent can never run a snippet to open a new tab or reconnect manually. The sessionID is effectively permanently stuck until a page target happens to appear in that browser. Consider clearing the recovery entry on this path (e.g. `v4TimeoutRecovery.delete(sessionID)` before throwing) so a subsequent snippet can run and let the agent reconnect explicitly, or otherwise provide a way out of the recovery state.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread packages/bcode-browser/src/cdp/session.ts Outdated
Comment thread packages/bcode-browser/src/browser-execute.ts Outdated
const page = targets.find((target) => target.targetId === recoveryState?.targetId)
?? targets.find((target) => target.type === "page" && !target.url.startsWith("chrome://"))
if (recoveryState && !page) {
throw new Error("No page target available after browser_execute timeout")

@cubic-dev-ai cubic-dev-ai Bot Aug 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a timeout recovery cannot find the preserved page target (and there is no fallback page target either), this branch throws and leaves v4TimeoutRecovery[sessionID] set. On every later browser_execute for the same session, ensureCloudConnected re-enters the recovery path (v4Bootstrapped.has(sessionID) && !recoveryState is false), skips the already-open connect, hits getTargets, finds no page, and throws the same error again — and because ensureCloudConnected runs before the snippet is executed, the agent can never run a snippet to open a new tab or reconnect manually. The sessionID is effectively permanently stuck until a page target happens to appear in that browser. Consider clearing the recovery entry on this path (e.g. v4TimeoutRecovery.delete(sessionID) before throwing) so a subsequent snippet can run and let the agent reconnect explicitly, or otherwise provide a way out of the recovery state.

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 312:

<comment>When a timeout recovery cannot find the preserved page target (and there is no fallback page target either), this branch throws and leaves `v4TimeoutRecovery[sessionID]` set. On every later `browser_execute` for the same session, `ensureCloudConnected` re-enters the recovery path (`v4Bootstrapped.has(sessionID) && !recoveryState` is false), skips the already-open connect, hits `getTargets`, finds no page, and throws the same error again — and because `ensureCloudConnected` runs before the snippet is executed, the agent can never run a snippet to open a new tab or reconnect manually. The sessionID is effectively permanently stuck until a page target happens to appear in that browser. Consider clearing the recovery entry on this path (e.g. `v4TimeoutRecovery.delete(sessionID)` before throwing) so a subsequent snippet can run and let the agent reconnect explicitly, or otherwise provide a way out of the recovery state.</comment>

<file context>
@@ -286,27 +294,33 @@ export const make = Effect.fn("BrowserExecute.make")(function* (dataDir: string)
+    const page = targets.find((target) => target.targetId === recoveryState?.targetId)
+      ?? targets.find((target) => target.type === "page" && !target.url.startsWith("chrome://"))
+    if (recoveryState && !page) {
+      throw new Error("No page target available after browser_execute timeout")
+    }
     if (page) await session.use(page.targetId)
</file context>
Fix with cubic

@MagMueller MagMueller Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superseded by the latest revision: the recovery map and target lookup were removed entirely. The next invocation continues on the existing Session without a pre-snippet recovery path.

@MagMueller MagMueller changed the title fix(browser): reconnect after tool timeout fix(browser): preserve session after tool timeout Aug 2, 2026
@MagMueller
MagMueller force-pushed the timeout-recovery branch 4 times, most recently from e108833 to d7f3e80 Compare August 2, 2026 16:31

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread packages/bcode-browser/src/cdp/session.ts
@MagMueller
MagMueller merged commit 7708a57 into main Aug 3, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant