can1357/oh-my-pi · error
RPC chunk received before protocol negotiation
Error message
RPC chunk received before protocol negotiation
What it means
During start(), the client reads the child's stdout. If a `rpc_chunk` frame arrives before protocol negotiation completed (protocolV2Enabled still false), the client throws because chunked framing cannot be decoded without an agreed protocol version.
Source
Thrown at packages/coding-agent/src/modes/rpc/rpc-client.ts:377
} catch {
// The process may already have exited.
}
await this.#waitForExit(child);
for (const request of pendingRequests) request.reject(error);
};
// Process lines in background, intercepting the ready signal.
const lines = readJsonl(child.stdout, this.#abortController.signal);
void (async () => {
for await (const line of lines) {
if (!readySettled && isRecord(line) && line.type === "ready") {
protocolV2Supported = supportsRpcProtocolV2(line);
readySettled = true;
readyResolve();
continue;
}
if (isRecord(line) && line.type === "rpc_chunk" && !protocolV2Enabled)
throw new Error("RPC chunk received before protocol negotiation");
const decoded = frameDecoder.push(line);
if (decoded) this.#handleLine(decoded);
}
// A closed stdout is terminal even if the child remains alive. Startup
// failures are reaped by the readyPromise catch below; established
// workers are reaped here so pending requests cannot hang indefinitely.
if (!readySettled) {
// Stdout can close before the exit reaper finishes draining stderr.
// child.exited settles only after the stderr tail is complete (for
// nonzero exits), so give it a bounded head start: the exit watcher
// below was registered first and rejects with the real stderr text
// instead of an empty "Stderr:" (flaked under full-suite load).
await Promise.race([child.exited.catch(() => {}), Bun.sleep(250)]);
if (readySettled) return;
readySettled = true;
readyReject(new Error(`Agent output stream ended before ready. Stderr: ${child.peekStderr()}`));
return;
}View on GitHub (pinned to 9690622007)
Solutions
- Ensure the spawned binary is the matching/supported version that performs protocol negotiation before sending rpc_chunk frames.
- Check any binary-path override (env/config) points at the correct omp binary.
- Upgrade both sides so they agree on the protocol before chunked framing.
- Report a bug if both sides are at expected versions — this indicates a broken handshake order.
Example fix
// before
new Worker(incompatibleChildBinary); // sends rpc_chunk immediately
// after
const client = new RpcClient({ binPath: matchedOmpBinary });
await client.start(); Defensive patterns
Strategy: try-catch
Validate before calling
const binPath = process.env.OMP_BIN ?? defaultOmpPath;
if (!existsSync(binPath)) throw new Error("child binary missing"); Try / catch
try {
await client.start();
} catch (err) {
if ((err as Error).message.includes("before protocol negotiation")) {
logger.error("child sent frames before handshake; binary version mismatch");
// restart with a known-good binary
} else throw err;
} Prevention
- Pin the child binary version alongside the host.
- Avoid overriding the binary path with arbitrary builds.
- Keep host and child released from the same build.
- Log child stderr to diagnose handshake-order violations.
When it happens
Trigger: The spawned child (wrong or newer binary) starts emitting rpc_chunk frames immediately instead of first responding to the negotiate_protocol handshake line.
Common situations: Version mismatch between the host and the child agent binary — the resolved binary on PATH is a different build than expected, or a custom binary override points at an incompatible client that skips negotiation.
Related errors
- RPC protocol v2 negotiation failed
- RPC protocol v2 negotiation failed
- ready.supportedProtocolVersions must be integers
- Replacement text is not valid UTF-8: {err}
- invalid glob `{pattern}`: {error}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/9ed1c19c905aa9ea.
Report an issue: GitHub.