Skip to content

RpcClient: pending requests wait 30s for a reply when the child process is already dead #4764

@gustavhartz

Description

@gustavhartz

What happened?

When the child process spawned by RpcClient dies (crash, OOM, kill), in-flight client.send() calls don't notice. They sit waiting for a reply until the 30s timer trips, then reject with "Timeout waiting for response..." — no hint that the child has been dead the whole time.

Also: any stdin.write() to the dead pipe during that window throws EPIPE asynchronously and can crash the parent if unhandled.

stop() works fine — that path drives the exit itself. Crash exits are the gap.

Steps to reproduce

import { RpcClient } from "@earendil-works/pi-coding-agent/rpc";

const client = new RpcClient({ agentPath: "/bin/false" }); // exits immediately
await client.start();
const t0 = Date.now();
try { await client.send({ type: "ping" }); } catch (e) {
  console.log(Date.now() - t0, e.message); // ~30000, "Timeout waiting for response..."
}

Expected behavior

Reject in <100ms with the actual exit code/signal/stderr. Don't let EPIPE on the dead stdin escape.

Version

@earendil-works/pi-coding-agent 0.75.3.


Fix: one listener in start(), after attachJsonlLineReader:

this.process.on("exit", (code, signal) => {
  const err = new Error(`Agent process exited (code=${code} signal=${signal}). Stderr: ${this.stderr}`);
  for (const { reject } of this.pendingRequests.values()) reject(err);
  this.pendingRequests.clear();
});
this.process.stdin?.on("error", () => { /* exit handler settles pending */ });

Metadata

Metadata

Assignees

Labels

inprogressIssue is being worked on

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions