can1357/oh-my-pi · error
Client not started
Error message
Client not started
What it means
#send() throws synchronously if the client has no spawned process/stdin — i.e. start() was never called, or stop()/process exit already cleared this.#process. No RPC command can be written without an established child process.
Source
Thrown at packages/coding-agent/src/modes/rpc/rpc-client.ts:1128
return;
}
if (!isAgentSessionEvent(data)) return;
for (const listener of this.#sessionEventListeners) {
listener(data);
}
if (!isAgentEvent(data)) return;
for (const listener of this.#eventListeners) {
listener(data);
}
}
#send(command: RpcCommandBody, timeoutMs = 30_000): Promise<RpcResponse> {
if (!this.#process?.stdin) {
throw new Error("Client not started");
}
const id = `req_${++this.#requestId}`;
const fullCommand = { ...command, id } as RpcCommand;
const { promise, resolve, reject } = Promise.withResolvers<RpcResponse>();
let settled = false;
const timeoutId = this.#startTimeout(timeoutMs, () => {
if (settled) return;
this.#pendingRequests.delete(id);
settled = true;
reject(
new Error(`Timeout waiting for response to ${command.type}. Stderr: ${this.#process?.peekStderr() ?? ""}`),
);
});
this.#pendingRequests.set(id, {
resolve: response => {
if (settled) return;View on GitHub (pinned to 9690622007)
Solutions
- await client.start() before issuing any command.
- Check the client's running state before sending, especially after stop().
- Re-create and start a client after the previous one was stopped or crashed.
- Serialize your lifecycle: gate commands on a ready-promise so they wait for start to finish.
Example fix
// before const client = new RpcClient(opts); const msgs = await client.getMessages(); // throws // after const client = new RpcClient(opts); await client.start(); const msgs = await client.getMessages();
Defensive patterns
Strategy: try-catch
Validate before calling
if (!client.isRunning()) await client.start();
Try / catch
try {
const res = await client.sendCommand(cmd);
} catch (err) {
if ((err as Error).message === "Client not started") {
await client.start();
} else throw err;
} Prevention
- Always await start() in an init function before any command.
- Register commands only after start resolves.
- Handle process exit by restarting the client, not reusing it.
- Keep a single lifecycle owner so stop() cannot race commands.
When it happens
Trigger: Calling any command that routes through #send before start(), or after stop() or an unexpected child exit cleared this.#process.
Common situations: Forgetting to await client.start() during setup; issuing commands after the session was stopped; a crash of the child process leaving the client without a process; async races where a queued command fires post-stop.
Related errors
- Client already started
- bridge call {name!r} failed
- Daemon broker client is closed
- LSP mux already listening on ${endpoint}
- Host URI write failed for ${url.href}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/347fad1ea6ced608.
Report an issue: GitHub.