{"record":{"id":"e76eb8b02a185e5c","repo":"can1357/oh-my-pi","slug":"rpc-protocol-v2-negotiation-failed","errorCode":null,"errorMessage":"RPC protocol v2 negotiation failed","messagePattern":"RPC protocol v2 negotiation failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/modes/rpc/rpc-client.ts","lineNumber":456,"sourceCode":"\t\t// Timeout to prevent hanging forever\n\t\tconst readyTimeout = this.#startTimeout(30000, () => {\n\t\t\tif (readySettled) return;\n\t\t\treadySettled = true;\n\t\t\treadyReject(new Error(`Timeout waiting for agent to become ready. Stderr: ${child.peekStderr()}`));\n\t\t});\n\n\t\ttry {\n\t\t\tawait readyPromise;\n\t\t\tif (protocolV2Supported) {\n\t\t\t\tprotocolV2Enabled = true;\n\t\t\t\tconst response = await this.#send({ type: \"negotiate_protocol\", protocolVersion: 2 });\n\t\t\t\tif (\n\t\t\t\t\t!response.success ||\n\t\t\t\t\tresponse.command !== \"negotiate_protocol\" ||\n\t\t\t\t\t!isRecord(response.data) ||\n\t\t\t\t\tresponse.data.protocolVersion !== 2\n\t\t\t\t)\n\t\t\t\t\tthrow new Error(\"RPC protocol v2 negotiation failed\");\n\t\t\t\tthis.#protocolVersion = 2;\n\t\t\t}\n\t\t\tif (this.#customTools.length > 0) {\n\t\t\t\tawait this.setCustomTools(this.#customTools);\n\t\t\t}\n\t\t} catch (cause) {\n\t\t\t// Startup failed after spawning the child. Reap it before returning\n\t\t\t// so a retry cannot inherit a live worker or its session lock.\n\t\t\tconst error = cause instanceof Error ? cause : new Error(String(cause));\n\t\t\tawait reapAfterOutputFailure(error);\n\t\t\tthrow cause;\n\t\t} finally {\n\t\t\tclearTimeout(readyTimeout);\n\t\t}\n\t}\n\n\t/**\n\t * Stop the RPC agent process.","sourceCodeStart":438,"sourceCodeEnd":474,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/modes/rpc/rpc-client.ts#L438-L474","documentation":"After sending the negotiate_protocol command, start() validates the response: it must be successful, carry command 'negotiate_protocol', and report data.protocolVersion === 2. Any deviation throws because the client requires v2 to proceed (chunked framing, custom tools).","triggerScenarios":"The child responds success:false to negotiate_protocol, echoes a different command, omits data, or reports a protocolVersion other than 2.","commonSituations":"Running an older child binary that only speaks protocol v1 or rejects negotiation; spawning a non-omp process; a custom RPC server implementation that mishandles the negotiate command.","solutions":["Use a child binary that supports RPC protocol v2 (update omp / the spawned CLI).","Inspect the child's stderr/log for why negotiation failed.","Remove any override forcing an old binary version.","If v1 is acceptable, use a client path that does not require v2 negotiation."],"exampleFix":"// before\nconst client = new RpcClient({ binPath: oldOmpV1 });\n// after\nconst client = new RpcClient({ binPath: updatedOmpWithV2 });","handlingStrategy":"retry","validationCode":"const version = await probeChildProtocolVersion(binPath);\nif (version < 2) throw new Error(\"child does not support RPC protocol v2\");","typeGuard":null,"tryCatchPattern":"try {\n  await client.start();\n} catch (err) {\n  if ((err as Error).message === \"RPC protocol v2 negotiation failed\") {\n    const retry = new RpcClient({ ...opts, binPath: updatedBinPath });\n    await retry.start(); // retry with a known-v2 binary\n  } else throw err;\n}","preventionTips":["Update omp so both sides speak protocol v2.","Probe child capabilities before start if you support multiple versions.","Don't point the client at foreign RPC servers.","Watch release notes for protocol bumps."],"tags":["rpc","protocol","negotiation","version-mismatch"],"backgroundTag":"protocol-version-mismatch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}