can1357/oh-my-pi · error

${argv[0]} exited with code ${exitCode} before reporting a t

Error message

${argv[0]} exited with code ${exitCode} before reporting a tunnel URL

What it means

spawnUrlTunnel launches a tunnel binary (cloudflared, ngrok, tailscale, etc.) and scans its stdout for a ready-marker URL. This error is thrown when the child process terminates with a non-zero (or any) exit code before ever printing a tunnel URL, so the broker cannot advertise a public base URL. It is the tunnel-binary crash path of the ready-wait loop.

Source

Thrown at packages/coding-agent/src/blob-broker/exposure.ts:279

		if (text.length > scanned) {
			if (baseUrl === undefined) {
				for (const line of text.slice(scanned).split("\n")) {
					const url = extract(line);
					if (url) {
						baseUrl = normalizeBaseUrl(url);
						break;
					}
				}
				scanned = text.lastIndexOf("\n") + 1;
			}
			// The URL banner can precede edge registration (cloudflared prints the
			// hostname before any connection is live); wait for the ready marker.
			if (baseUrl !== undefined && (!readyPattern || readyPattern.test(text))) {
				return { proc, baseUrl };
			}
		}
		if (exitCode !== null) {
			throw new Error(`${argv[0]} exited with code ${exitCode} before reporting a tunnel URL`);
		}
		await Bun.sleep(150);
	}
	killTunnelProcess(proc);
	throw new Error(`${argv[0]} did not report a tunnel URL within ${READY_TIMEOUT_MS / 1000}s`);
}

function processExposure(kind: ExposureKind, baseUrl: string, proc: Bun.Subprocess): ActiveExposure {
	proc.unref();
	return {
		kind,
		baseUrl,
		exited: proc.exited.then(() => undefined),
		stop: () => killTunnelProcess(proc),
	};
}

/**

View on GitHub (pinned to 9690622007)

Solutions

  1. Read the tunnel binary's stderr output from the spawn (it is captured but not in this error) and run the same argv manually to see the real failure.
  2. Verify credentials: ngrok authtoken, cloudflared tunnel token, bore secret.
  3. Confirm the binary runs: check version (`cloudflared --version`) and that CLI flags match the installed version.
  4. Check network egress/firewall allows the tunnel's outbound connection.
  5. Switch exposure kind (e.g. to `direct` on LAN) as a fallback.

Example fix

// before
options: { authtoken: "<old-expired-token>" }
// after
// refresh the token in ngrok dashboard, then
options: { authtoken: process.env.NGROK_AUTHTOKEN }
Defensive patterns

Strategy: validation

Validate before calling

const probe = Bun.spawnSync([binary, "--version"], { stderr: "pipe" });
if (probe.exitCode !== 0) throw new Error(`tunnel binary ${binary} not runnable: ${probe.stderr.toString()}`);

Try / catch

try {
  const exposure = await startExposure(config);
} catch (err) {
  if (err instanceof Error && err.message.includes("exited with code")) {
    logger.warn("tunnel exited before ready; falling back", { kind: config.kind });
    // inspect tunnel stderr or fall back to another exposure kind
  } else throw err;
}

Prevention

When it happens

Trigger: Calling startExposure with a tunnel kind (cloudflared/ngrok/bore/pinggy/devtunnel/zrok/localhost-run) whose binary exits during the 30s READY_TIMEOUT_MS window before emitting a parseable tunnel URL line — e.g. bad auth token, port conflict, invalid flags, or the binary crashes on startup.

Common situations: Expired or wrong ngrok authtoken; cloudflared quick-tunnel blocked by network egress rules; bore/zrok server unreachable so the client exits; binary version changed its CLI flags and dies immediately; sandboxed/container environments that cannot open outbound connections.

Related errors


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