can1357/oh-my-pi · error · ToolError

app.surface must be a surface UUID (e.g. CMUX_SURFACE_ID), n

Error message

app.surface must be a surface UUID (e.g. CMUX_SURFACE_ID), not a 'surface:N' ref; omit it to open a new split

What it means

When opening a cmux-attached tab, the resolved surface identifier must be a raw surface UUID. If it starts with the legacy 'surface:' ref prefix, the library refuses it because the cmux API expects a UUID in app.surface (e.g. CMUX_SURFACE_ID). Omitting app.surface opens a new split instead.

Source

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

		ownerSessionId: opts.ownerSessionId,
	};
	worker.onMessage(msg => handleTabMessage(tab, msg));
	tabs.set(name, tab);
	// Durably record ownership so another live omp process can reap this page if
	// this process dies abnormally before its own teardown closes the tab.
	const scope = sharedScopeOf(browser);
	if (scope) void recordSharedTarget(scope, info.targetId);
	return { tab, created: true };
}

async function acquireCmuxTab(
	name: string,
	browser: CmuxBrowserHandle,
	opts: AcquireTabOptions,
): Promise<AcquireTabResult> {
	const attachedSurface = opts.cmuxSurface ?? browser.surface;
	if (attachedSurface?.startsWith("surface:")) {
		throw new ToolError(
			"app.surface must be a surface UUID (e.g. CMUX_SURFACE_ID), not a 'surface:N' ref; omit it to open a new split",
		);
	}

	let surfaceId = attachedSurface;
	let initialUrl = opts.url;
	let ownsSurface = false;
	try {
		if (!surfaceId) {
			const params: Record<string, unknown> = { url: opts.url ?? "about:blank", focus: false };
			if (process.env.CMUX_WORKSPACE_ID) params.workspace_id = process.env.CMUX_WORKSPACE_ID;
			if (process.env.CMUX_SURFACE_ID) params.surface_id = process.env.CMUX_SURFACE_ID;
			const result = await browser.client.request("browser.open_split", params, { timeoutMs: opts.timeoutMs });
			if (typeof result.surface_id !== "string" || result.surface_id.length === 0) {
				throw new ToolError("cmux browser.open_split did not return a surface_id");
			}
			surfaceId = result.surface_id;
			ownsSurface = true;

View on GitHub (pinned to 9690622007)

Solutions

  1. Pass the raw UUID (value of CMUX_SURFACE_ID) as the surface instead of a 'surface:N' ref.
  2. Omit the surface option entirely so the call opens a new split.
  3. Convert at the boundary: strip/resolve the 'surface:' prefix to its UUID before calling the tool.
  4. Update stored configuration/scripts that still record surface refs in the old format.

Example fix

// before
await browserTool({ action: "open", cmuxSurface: "surface:3" });
// after
await browserTool({ action: "open", cmuxSurface: process.env.CMUX_SURFACE_ID }); // raw UUID
Defensive patterns

Strategy: validation

Validate before calling

const surface = opts.cmuxSurface ?? process.env.CMUX_SURFACE_ID;
if (surface && surface.startsWith("surface:")) {
  throw new Error(`Pass the surface UUID, not '${surface}'; omit to open a new split`);
}

Type guard

function isSurfaceUuid(v: string): boolean {
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);
}

Prevention

When it happens

Trigger: Calling browser open with cmuxSurface set (or a browser handle whose .surface carries) a value like 'surface:3' instead of the UUID that CMUX_SURFACE_ID exposes.

Common situations: Copying a surface ref from cmux UI/logs instead of reading the CMUX_SURFACE_ID env var; stale code passing the old 'surface:N' format after a cmux API change; hardcoding a ref captured from a previous session.

Related errors


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