can1357/oh-my-pi · error

Unsupported RPC protocol version: ${version}

Error message

Unsupported RPC protocol version: ${version}

What it means

RpcFrameEncoder.setProtocolVersion() only accepts protocol versions 1 or 2. Passing any other version number throws immediately, since the encoder cannot emit frames for an unknown wire protocol.

Source

Thrown at packages/coding-agent/src/modes/rpc/rpc-frame.ts:277

		if (serializedFrameBytes(json) <= MAX_RPC_FRAME_BYTES) return `${json}\n`;
	}

	return `${JSON.stringify(overflowFrame(compacted))}\n`;
}

/** Serialize a complete JSONL frame while enforcing the transport byte ceiling. */
export function encodeRpcFrame(frame: object, streamedMessageCount = 0, streamedMessages?: readonly unknown[]): string {
	return encodeRpcFrameFromJson(frame, JSON.stringify(frame), streamedMessageCount, streamedMessages);
}

/** Stateful encoder that tracks which messages a client has already received. */
export class RpcFrameEncoder {
	#streamedMessages: unknown[] = [];
	#protocolVersion: RpcProtocolVersion = 1;
	#chunkCounter = 0;

	setProtocolVersion(version: number): void {
		if (version !== 1 && version !== 2) throw new Error(`Unsupported RPC protocol version: ${version}`);
		this.#protocolVersion = version;
	}

	/**
	 * Encode one logical frame into physical JSONL lines. Encoder bookkeeping runs
	 * eagerly; only chunk emission is lazy, so a chunked result can be streamed to
	 * stdout with backpressure without holding the whole transport in memory. The
	 * returned iterable MUST be fully consumed exactly once.
	 */
	encodeFrames(frame: object): Iterable<string> {
		if (isRecord(frame) && frame.type === "agent_start") this.#streamedMessages = [];
		const json = JSON.stringify(frame);
		let frames: Iterable<string>;
		let singleFrame: string | undefined;
		if (this.#protocolVersion === 2 && serializedFrameBytes(json) > MAX_RPC_FRAME_BYTES) {
			const compacted = compactTerminalFrame(frame, this.#streamedMessages.length, this.#streamedMessages);
			// Reuse the original serialization when compaction was a no-op.
			const compactedJson = compacted === frame ? json : JSON.stringify(compacted);

View on GitHub (pinned to 9690622007)

Solutions

  1. Only call setProtocolVersion with 1 or 2; clamp or map other values before the call.
  2. Ensure the handshake passes a number, not a string: setProtocolVersion(Number(handshake.version)).
  3. Upgrade both processes to matching versions of the RPC implementation so the advertised version is supported.
  4. Default to version 1 when the negotiated version is unknown rather than forwarding it blindly.

Example fix

// before
encoder.setProtocolVersion(handshake.protocolVersion); // e.g. 3 from newer peer
// after
const v = handshake.protocolVersion;
encoder.setProtocolVersion(v === 2 ? 2 : 1);
Defensive patterns

Strategy: validation

Validate before calling

const SUPPORTED_VERSIONS = [1, 2] as const;
function assertSupportedVersion(v: number): asserts v is 1 | 2 {
  if (v !== 1 && v !== 2) throw new Error(`client cannot speak protocol ${v}`);
}
assertSupportedVersion(handshake.protocolVersion);

Type guard

function isSupportedProtocolVersion(v: unknown): v is 1 | 2 {
  return v === 1 || v === 2;
}

Try / catch

try {
  encoder.setProtocolVersion(negotiated);
} catch (err) {
  if (String(err.message).startsWith("Unsupported RPC protocol version")) {
    encoder.setProtocolVersion(1); // graceful fallback
  } else throw err;
}

Prevention

When it happens

Trigger: Calling encoder.setProtocolVersion(3) (or 0, -1, 2.5, NaN) during RPC client/server handshake when the peers advertise an unsupported protocolVersion.

Common situations: Client and server are different builds with mismatched protocol versions; a new protocol version was introduced in the server but the embedded/older encoder doesn't know it; handshake code forwards a raw string ('2') instead of a number.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/23a8af01b8bae590. Report an issue: GitHub.