can1357/oh-my-pi · error · ToolError

Target ${targetId} is no longer available on the attached br

Error message

Target ${targetId} is no longer available on the attached browser

What it means

#findAttachedTarget iterates `browser.targets()` comparing each target's id to the requested targetId; when no target matches, it throws that the target is no longer available. It is the authoritative 'this tab does not exist in the attached browser' error.

Source

Thrown at packages/coding-agent/src/tools/browser/tab-worker.ts:1139

		} catch (error) {
			// A failed headless init leaves the worker's page orphaned in the shared
			// browser (the supervisor retries with a fresh worker), so close it before
			// reporting. Attach mode adopts an existing target — never close it.
			const page = this.#page;
			if (payload.mode === "headless" && page && !page.isClosed()) {
				await page.close().catch(() => undefined);
			}
			this.#transport.send({ type: "init-failed", error: errorPayload(error) });
		}
	}

	async #findAttachedTarget(targetId: string): Promise<Target> {
		if (!this.#browser) throw new ToolError("Browser is not connected");
		for (const target of this.#browser.targets()) {
			if ((await targetIdForTarget(target).catch(() => "")) !== targetId) continue;
			return target;
		}
		throw new ToolError(`Target ${targetId} is no longer available on the attached browser`);
	}

	/**
	 * Tell the omp browser relay this worker drives the adopted page, so the
	 * relay adds it to the per-window "omp" tab group. Best-effort: plain CDP
	 * backends (real Chrome, cmux) reject the relay-private method.
	 */
	async #claimRelayTarget(page: Page): Promise<void> {
		let session: CDPSession | undefined;
		try {
			session = await page.createCDPSession();
			// Puppeteer's protocol map cannot express the relay-private method; the
			// send signature is otherwise identical.
			const raw = session as unknown as { send(method: string): Promise<unknown> };
			await raw.send("OMP.claimTarget");
		} catch {
			// Not the omp relay; nothing to claim.
		} finally {

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-list `browser.targets()` and select a currently valid targetId
  2. Reopen the target (open a new tab with the same URL) if the content is still needed
  3. Refresh cached target IDs after navigation or reconnect events
  4. Validate targetId against a fresh target list before each operation in long-running scripts

Example fix

// before
await worker.runInTab(cachedTargetId, code);
// after
const live = await worker.listTargets();
const id = live.find(t => t.url === expectedUrl)?.id ?? (await worker.openTab(expectedUrl)).id;
await worker.runInTab(id, code);
Defensive patterns

Strategy: validation

Validate before calling

const live = await worker.listTargets();
if (!live.some(t => t.id === targetId)) {
  targetId = (await worker.openTab(url)).id;
}

Type guard

function hasTarget(targets: {id:string}[], id: string): targets is {id:string}[] & {find(t: {id:string}): boolean} {
  return targets.some(t => t.id === id);
}

Try / catch

try {
  await worker.runInTab(targetId, code);
} catch (err) {
  if (err instanceof ToolError && err.message.includes('no longer available')) {
    const fresh = await worker.openTab(url);
    return worker.runInTab(fresh.id, code);
  }
  throw err;
}

Prevention

When it happens

Trigger: Adopting/operating on a targetId that was closed, navigated away, or never existed in the currently attached browser; stale target IDs from a previous session or after a page transitioned to a different target type.

Common situations: Script holds target IDs across a browser restart; user closed the tab; SPA/page navigation replaced the target; ID typo or ID copied from a different browser instance.

Related errors


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