{"record":{"id":"4c5688c232cbf810","repo":"can1357/oh-my-pi","slug":"killed-tab-json-stringify-name-was-killed","errorCode":null,"errorMessage":"killed ? `Tab ${JSON.stringify(name)} was killed: ${killed}. Reopen it.` : `Tab ${JSON.stringify(name)} is not alive. Open it first with action:\"open\".`","messagePattern":"killed \\? `Tab (.+?) was killed: (.+?)\\. Reopen it\\.` : `Tab (.+?) is not alive\\. Open it first with action:\"open\"\\.`","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/browser/tab-supervisor.ts","lineNumber":514,"sourceCode":"\t\tname,\n\t\t{ code: opts.code, timeoutMs: opts.timeoutMs, signal: opts.signal, session: opts.session },\n\t\t{\n\t\t\tcwd: opts.session.cwd,\n\t\t\tbrowserScreenshotDir: expandBrowserScreenshotDir(opts.session),\n\t\t\texcludeWebP: webpExclusionForModel(opts.session.getActiveModel?.()),\n\t\t},\n\t);\n}\n\nasync function runInTabWithSnapshot(\n\tname: string,\n\topts: { code: string; timeoutMs: number; signal?: AbortSignal; session?: ToolSession },\n\tsnapshot: SessionSnapshot,\n): Promise<RunResultOk> {\n\tconst tab = tabs.get(name);\n\tif (!tab || tab.state === \"dead\") {\n\t\tconst killed = killedTabs.get(name);\n\t\tthrow new ToolError(\n\t\t\tkilled\n\t\t\t\t? `Tab ${JSON.stringify(name)} was killed: ${killed}. Reopen it.`\n\t\t\t\t: `Tab ${JSON.stringify(name)} is not alive. Open it first with action:\"open\".`,\n\t\t);\n\t}\n\tif (tab.pending.size > 0) throw new ToolError(`Tab ${JSON.stringify(name)} is busy`);\n\tconst id = Snowflake.next();\n\tconst { promise, resolve, reject } = Promise.withResolvers<RunResultOk>();\n\t// `releaseTab` calls `pending.reject(closeError)` when the tab dies\n\t// out from under an in-flight run (sibling `browser close --all`,\n\t// session-scoped reap, etc.). Both backends below MUST end up awaiting\n\t// this same `promise` so:\n\t//   1. The caller sees `Tab ... was closed` immediately instead of\n\t//      blocking to the run's timeout, and\n\t//   2. `reject(...)` always has an attached handler — a zero-consumer\n\t//      rejection would fire `unhandledRejection` and the CLI's\n\t//      top-level handler would tear the whole session down, killing\n\t//      every other tab and subagent sharing the process (issue #4499).","sourceCodeStart":496,"sourceCodeEnd":532,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/browser/tab-supervisor.ts#L496-L532","documentation":"runInTabWithSnapshot looks the named tab up in the registry; if it is missing or marked dead it throws ToolError telling the caller the tab was killed (with the kill reason) or simply is not alive, and to open it first. This guards against running code in a browser tab that no longer exists.","triggerScenarios":"Calling eval/navigate-style actions on a tab name that was never opened, was closed, or whose browser crashed (state 'dead'); killedTabs may carry the reason of a previous kill.","commonSituations":"Tab killed by 'browser close --all' or a session reap while the agent still references it; browser process crashed and all tabs went dead; typo in tab name; script assumes a tab persists across turns but session-scoped cleanup closed it.","solutions":["Re-open the tab with action:\"open\" (same name) and retry the operation.","If it was killed, read the kill reason in the message and address it (e.g. avoid the triggering action).","Check liveness before running: try getTab/inspect the tab list, or wrap the run in try-catch and reopen on this error.","Avoid the crash path: keep the browser process alive and do not issue close --all while tabs are still needed."],"exampleFix":"// before\nawait browserTool({ action: \"eval\", name: \"docs\", code }); // throws if dead\n// after\ntry {\n  await browserTool({ action: \"eval\", name: \"docs\", code });\n} catch (e) {\n  if (/not alive|was killed/.test(e.message)) {\n    await browserTool({ action: \"open\", name: \"docs\", url });\n    await browserTool({ action: \"eval\", name: \"docs\", code });\n  } else throw e;\n}","handlingStrategy":"try-catch","validationCode":"const tab = getTab(name);\nif (!tab || tab.state === \"dead\") {\n  await browserTool({ action: \"open\", name, url });\n}","typeGuard":"function isRunnableTab(t: ReturnType<typeof getTab>): boolean {\n  return !!t && t.state !== \"dead\";\n}","tryCatchPattern":"try {\n  await runInTab(name, code);\n} catch (e) {\n  if (e instanceof ToolError && /was killed|not alive/.test(e.message)) {\n    await browserTool({ action: \"open\", name, url });\n    await runInTab(name, code);\n  } else throw e;\n}","preventionTips":["Reopen tabs at the start of each turn if session cleanup may have closed them","Track kill reasons and avoid the actions that triggered kills","Don't issue 'browser close --all' while tabs are still needed","Check tab liveness before long-running scripts"],"tags":["browser","state","tab-lifecycle"],"backgroundTag":"tab-not-alive","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}