can1357/oh-my-pi · error · Error

Claude command exited before /v1/messages completed${outputS

Error message

Claude command exited before /v1/messages completed${outputSuffix()}

What it means

The fallback case of the capture race: the Claude command exited (without an error object) before any /v1/messages exchange was captured, and even after the 250ms late-capture grace window no exchange arrived. The trace throws with any captured output appended so the developer can see why Claude quit.

Source

Thrown at packages/coding-agent/src/cli/claude-trace-cli.ts:781

		const first = await Promise.race([captureRace, ptyRace]);
		if (first.kind === "capture") {
			await shutdownPty(session, runPromise);
			return first.exchange;
		}
		if (first.kind === "capture-error") {
			throw new Error(`${errorMessage(first.error)}${outputSuffix()}`);
		}
		const late = await Promise.race([captureRace, Bun.sleep(250).then(() => ({ kind: "late-timeout" as const }))]);
		if (late.kind === "capture") {
			await shutdownPty(session, runPromise);
			return late.exchange;
		}
		if (first.kind === "pty-error") {
			throw new Error(
				`Claude command failed before /v1/messages completed: ${errorMessage(first.error)}${outputSuffix()}`,
			);
		}
		throw new Error(`Claude command exited before /v1/messages completed${outputSuffix()}`);
	} finally {
		terminal.dispose();
		await proxy.stop();
	}
}

export async function runClaudeTraceCommand(args: ClaudeTraceCommandArgs = {}): Promise<void> {
	process.stderr.write(
		`Starting Claude trace proxy on ${args.host ?? DEFAULT_PROXY_HOST}:${args.port ?? DEFAULT_PROXY_PORT}\n`,
	);
	const exchange = await runClaudeMessagesCapture(args);
	const output = args.json ? `${JSON.stringify(exchange, null, 2)}\n` : formatCapturedMessagesExchange(exchange);
	process.stdout.write(output.endsWith("\n") ? output : `${output}\n`);
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Read the outputSuffix() capture output included in the message — it shows what claude printed before exiting.
  2. Run `claude -p 'hi'` manually to see the early exit (help text, TOS prompt, auth wall) and fix it.
  3. Increase --input-delay so the prompt is typed after claude fully initializes.
  4. Re-run the trace; if the capture keeps missing fast exchanges, check proxy/TLS setup so the exchange is recorded on time.

Example fix

// before: claude exits showing onboarding prompt
$ omp claude-trace
// after: pre-accept onboarding, then trace
$ claude  # complete onboarding once
$ omp claude-trace
Defensive patterns

Strategy: validation

Validate before calling

const check = await Bun.$`claude --version`.quiet().nothrow();
if (check.exitCode !== 0) throw new Error("claude CLI missing or failing at startup");

Try / catch

try {
  const exchange = await runTrace();
} catch (err) {
  if ((err as Error).message.includes("exited before /v1/messages completed")) {
    // inspect the captured output appended to the message for claude's final output
  } else throw err;
}

Prevention

When it happens

Trigger: The PTY session observes claude exiting normally-but-early (exit event, not an error throw) with no /v1/messages capture completing — e.g. claude exited instantly, rejected the prompt interactively, or the session ended before the API call.

Common situations: claude CLI printed a usage/help screen and exited; interactive confirmation or TOS-acceptance prompt stalled then exited; extremely fast failure where pty-error vs exit classification landed on the exit branch; network so fast the capture missed the exchange (rare).

Related errors


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