can1357/oh-my-pi · error

Client already started

Error message

Client already started

What it means

RpcClient.start() is a lifecycle guard: it awaits any pending reaping, then throws if this.#process is already set. The client forbids double-start so a second spawn cannot orphan a running child process or clobber its abort controller.

Source

Thrown at packages/coding-agent/src/modes/rpc/rpc-client.ts:303

	#extensionUiListeners: Set<(req: RpcExtensionUIRequest) => void> = new Set();
	#abortController = new AbortController();

	constructor(private options: RpcClientOptions = {}) {
		this.#customTools = [...(options.customTools ?? [])];
	}

	/**
	 * Start the RPC agent process.
	 *
	 * Safe to call again after {@link stop} on the same instance: a fresh
	 * {@link AbortController} is minted for each start, and any failure after
	 * the child spawn kills the child and clears internal state so callers may
	 * retry without leaking processes.
	 */
	async start(): Promise<void> {
		await this.#reaping;
		if (this.#process) {
			throw new Error("Client already started");
		}

		// Mint a fresh controller so a previous stop()'s abort does not
		// short-circuit the new stdout reader (issue #4079).
		this.#abortController = new AbortController();
		this.#protocolVersion = 1;

		const cliPath = this.options.cliPath ?? "dist/cli.js";
		const args = ["--mode", "rpc"];

		if (this.options.provider) {
			args.push("--provider", this.options.provider);
		}
		if (this.options.model) {
			args.push("--model", this.options.model);
		}
		if (this.options.sessionDir) {
			args.push("--session-dir", this.options.sessionDir);

View on GitHub (pinned to 9690622007)

Solutions

  1. Call stop() before start() again, or make the caller idempotent: skip start when the client is already running.
  2. Guard with a running check: only start if not already running.
  3. Create a fresh RpcClient instance instead of reusing the started one.
  4. If the old process crashed, start() already awaits #reaping, so a completed reap makes start safe — just retry once.

Example fix

// before
await client.start();
await client.start(); // throws
// after
if (!client.isRunning()) await client.start();
Defensive patterns

Strategy: try-catch

Validate before calling

if (client.isRunning?.()) return; // already started, skip

Try / catch

try {
  await client.start();
} catch (err) {
  if ((err as Error).message === "Client already started") return; // idempotent start
  throw err;
}

Prevention

When it happens

Trigger: Calling start() (e.g. from a __enter__/init wrapper) twice on the same RpcClient without stop() in between — this.#process is non-null on the second call.

Common situations: Framework re-entry calling an init/setup method more than once (double-mount of a UI component, hot-reload re-running setup code, retry logic that forgets the first start succeeded).

Related errors


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