can1357/oh-my-pi · error · ToolError

killed ? `Tab ${JSON.stringify(name)} was killed: ${killed}.

Error message

killed ? `Tab ${JSON.stringify(name)} was killed: ${killed}. Reopen it.` : `Tab ${JSON.stringify(name)} is not alive. Open it first with action:"open".`

What it means

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.

Source

Thrown at packages/coding-agent/src/tools/browser/tab-supervisor.ts:514

		name,
		{ code: opts.code, timeoutMs: opts.timeoutMs, signal: opts.signal, session: opts.session },
		{
			cwd: opts.session.cwd,
			browserScreenshotDir: expandBrowserScreenshotDir(opts.session),
			excludeWebP: webpExclusionForModel(opts.session.getActiveModel?.()),
		},
	);
}

async function runInTabWithSnapshot(
	name: string,
	opts: { code: string; timeoutMs: number; signal?: AbortSignal; session?: ToolSession },
	snapshot: SessionSnapshot,
): Promise<RunResultOk> {
	const tab = tabs.get(name);
	if (!tab || tab.state === "dead") {
		const killed = killedTabs.get(name);
		throw new ToolError(
			killed
				? `Tab ${JSON.stringify(name)} was killed: ${killed}. Reopen it.`
				: `Tab ${JSON.stringify(name)} is not alive. Open it first with action:"open".`,
		);
	}
	if (tab.pending.size > 0) throw new ToolError(`Tab ${JSON.stringify(name)} is busy`);
	const id = Snowflake.next();
	const { promise, resolve, reject } = Promise.withResolvers<RunResultOk>();
	// `releaseTab` calls `pending.reject(closeError)` when the tab dies
	// out from under an in-flight run (sibling `browser close --all`,
	// session-scoped reap, etc.). Both backends below MUST end up awaiting
	// this same `promise` so:
	//   1. The caller sees `Tab ... was closed` immediately instead of
	//      blocking to the run's timeout, and
	//   2. `reject(...)` always has an attached handler — a zero-consumer
	//      rejection would fire `unhandledRejection` and the CLI's
	//      top-level handler would tear the whole session down, killing
	//      every other tab and subagent sharing the process (issue #4499).

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-open the tab with action:"open" (same name) and retry the operation.
  2. If it was killed, read the kill reason in the message and address it (e.g. avoid the triggering action).
  3. Check liveness before running: try getTab/inspect the tab list, or wrap the run in try-catch and reopen on this error.
  4. Avoid the crash path: keep the browser process alive and do not issue close --all while tabs are still needed.

Example fix

// before
await browserTool({ action: "eval", name: "docs", code }); // throws if dead
// after
try {
  await browserTool({ action: "eval", name: "docs", code });
} catch (e) {
  if (/not alive|was killed/.test(e.message)) {
    await browserTool({ action: "open", name: "docs", url });
    await browserTool({ action: "eval", name: "docs", code });
  } else throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const tab = getTab(name);
if (!tab || tab.state === "dead") {
  await browserTool({ action: "open", name, url });
}

Type guard

function isRunnableTab(t: ReturnType<typeof getTab>): boolean {
  return !!t && t.state !== "dead";
}

Try / catch

try {
  await runInTab(name, code);
} catch (e) {
  if (e instanceof ToolError && /was killed|not alive/.test(e.message)) {
    await browserTool({ action: "open", name, url });
    await runInTab(name, code);
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/4c5688c232cbf810. Report an issue: GitHub.