{"record":{"id":"c2c1d17c89c8b7e3","repo":"can1357/oh-my-pi","slug":"no-tab-with-id-ref-tabid","errorCode":null,"errorMessage":"No tab with id ${ref.tabId}","messagePattern":"No tab with id (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/browser/relay/bridge.ts","lineNumber":484,"sourceCode":"\t\t\tthis.#reply(conn, msg, {});\n\t\t} catch (err) {\n\t\t\tthis.#replyError(conn, msg, err instanceof Error ? err.message : String(err));\n\t\t}\n\t}\n\n\t/**\n\t * Drive the shared root `Runtime.enable` for a session and replay the live\n\t * contexts to it. Rejects if the root cycle fails so every joined caller\n\t * observes the failure instead of a spurious success.\n\t */\n\tasync #enableSessionRuntime(conn: CdpConnection, sessionId: string, ref: SessionRef): Promise<void> {\n\t\tconst prev = ref.runtimeState;\n\t\tconst epoch = ++ref.runtimeEpoch;\n\t\tref.runtimeState = \"enabled\";\n\t\tconst tab = this.#tabs.get(ref.tabId);\n\t\tif (!tab) {\n\t\t\tref.runtimeState = prev;\n\t\t\tthrow new Error(`No tab with id ${ref.tabId}`);\n\t\t}\n\t\ttry {\n\t\t\tawait this.#ensureRuntimeEnabled(tab);\n\t\t\t// A disable or newer enable may have taken ownership while the root\n\t\t\t// RPC was in flight; only the latest enable may replay or roll back.\n\t\t\tif (conn.sessions.get(sessionId) === ref && ref.runtimeEpoch === epoch && ref.runtimeState === \"enabled\") {\n\t\t\t\tthis.#replayRuntimeContexts(conn, sessionId, ref, tab);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (ref.runtimeEpoch === epoch) {\n\t\t\t\tref.runtimeState = prev;\n\t\t\t\tref.runtimeContexts.clear();\n\t\t\t}\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync #ensureRuntimeEnabled(tab: TabState): Promise<void> {","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/browser/relay/bridge.ts#L466-L502","documentation":"RelayBridge's #enableSessionRuntime looks up the tab registered for a relay tab reference before enabling the CDP runtime on it. If the tab map no longer contains ref.tabId (the tab was closed, the extension service worker restarted and lost its registry, or the reference is stale), it restores the previous runtimeState and throws Error(`No tab with id <id>`).","triggerScenarios":"Enabling a session on a tab ref whose underlying tab was closed or navigated away before enable; Chrome extension service worker restart clearing the in-memory tab registry; a stale tabId reused after Chrome renumbered tabs.","commonSituations":"Agent tool call targeting a tab the user closed mid-task; relay extension reloaded (Chrome update, manual reload) invalidating previously issued tab refs; long-running sessions holding tab IDs across extension lifecycles.","solutions":["Re-list tabs (query the relay for current tabs) and re-resolve the target tab to get a fresh, valid tabId before enabling","Catch the error and treat the tab as gone: reopen or re-attach to a new tab","Keep tab refs short-lived; re-acquire refs after any extension reload or Chrome restart","If the service worker restarts frequently, pin the extension or check Chrome's service-worker lifetime settings"],"exampleFix":"// before\nawait bridge.enable(tabRef, session);\n// after\ntry {\n  await bridge.enable(tabRef, session);\n} catch (err) {\n  if (err.message.startsWith(\"No tab with id\")) {\n    tabRef = await bridge.resolveTab(currentTabId); // refresh stale ref\n    await bridge.enable(tabRef, session);\n  } else throw err;\n}","handlingStrategy":"try-catch","validationCode":"// verify the tab still exists before enabling a session on it\nconst currentTabs = await bridge.listTabs();\nif (!currentTabs.some(t => t.id === ref.tabId)) {\n  ref = await bridge.resolveTab(await pickTargetTab()); // re-acquire fresh ref\n}","typeGuard":"function tabExists(ref: { tabId: number }, tabs: { id: number }[]): boolean {\n  return tabs.some(t => t.id === ref.tabId);\n}","tryCatchPattern":"try {\n  await bridge.enable(ref, session);\n} catch (err) {\n  if (err instanceof Error && /^No tab with id \\d+$/.test(err.message)) {\n    ref = await refreshTabRef(); // tab closed or extension restarted\n    await bridge.enable(ref, session);\n  } else throw err;\n}","preventionTips":["Re-list tabs after any user interaction that may close/reopen tabs","Re-acquire tab refs whenever the extension service worker restarts (Chrome update, manual reload)","Keep sessions short-lived; don't cache tab refs across long idle periods","Handle tab-closed lifecycle events to invalidate refs proactively"],"tags":["relay","extension","tab","stale-reference","chrome"],"backgroundTag":"stale-tab-reference","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}