From 00163685a95b2b4171c27338ac323e3380017190 Mon Sep 17 00:00:00 2001 From: frizynn Date: Sun, 22 Mar 2026 22:29:19 -0300 Subject: [PATCH] fix(rpc): resolve double-set race, missing error ID, and stream handler Fix three bugs in the RPC subsystem: 1. rpc-client.ts: Remove duplicate `pendingRequests.set(id, ...)` call that immediately gets overwritten. The first set stored bare resolve/reject without timeout cleanup, creating a race window where timeout could fire with the wrong handler. 2. rpc-mode.ts: Unknown command error response now preserves the request's id instead of returning `id: undefined`, fixing request-response correlation for unrecognized commands. 3. jsonl.ts: Add missing `error` event handler on the input stream to prevent unhandled exceptions, and include it in the cleanup function returned by `attachJsonlLineReader`. --- packages/pi-coding-agent/src/modes/rpc/jsonl.ts | 6 ++++++ packages/pi-coding-agent/src/modes/rpc/rpc-client.ts | 2 -- packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/pi-coding-agent/src/modes/rpc/jsonl.ts b/packages/pi-coding-agent/src/modes/rpc/jsonl.ts index 8962c7340..5392defef 100644 --- a/packages/pi-coding-agent/src/modes/rpc/jsonl.ts +++ b/packages/pi-coding-agent/src/modes/rpc/jsonl.ts @@ -48,11 +48,17 @@ export function attachJsonlLineReader(stream: Readable, onLine: (line: string) = } }; + const onError = (_err: Error) => { + // Stream errors are non-fatal for JSONL reading + }; + stream.on("data", onData); stream.on("end", onEnd); + stream.on("error", onError); return () => { stream.off("data", onData); stream.off("end", onEnd); + stream.off("error", onError); }; } diff --git a/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts b/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts index a3f91ecc4..319a7418c 100644 --- a/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +++ b/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts @@ -482,8 +482,6 @@ export class RpcClient { const fullCommand = { ...command, id } as RpcCommand; return new Promise((resolve, reject) => { - this.pendingRequests.set(id, { resolve, reject }); - const timeout = setTimeout(() => { this.pendingRequests.delete(id); reject(new Error(`Timeout waiting for response to ${command.type}. Stderr: ${this.stderr}`)); diff --git a/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts b/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts index dc02b4491..e41e5ac3b 100644 --- a/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +++ b/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts @@ -586,8 +586,8 @@ export async function runRpcMode(session: AgentSession): Promise { } default: { - const unknownCommand = command as { type: string }; - return error(undefined, unknownCommand.type, `Unknown command: ${unknownCommand.type}`); + const unknownCommand = command as { type: string; id?: string }; + return error(unknownCommand.id, unknownCommand.type, `Unknown command: ${unknownCommand.type}`); } } };