can1357/oh-my-pi · error

${label} must be an object

Error message

${label} must be an object

What it means

The daemon protocol parser validates every decoded payload structurally. record(value, label) is the generic object check used by parseDaemon* functions; any wire payload that is not a plain object (wrong type, null, array) fails with '<label> must be an object', where label names the offending field or message kind.

Source

Thrown at packages/coding-agent/src/launch/protocol.ts:153

/** Response envelope kept raw until matched with its pending operation. */
export type DaemonWireResponse = { id: string; ok: true; result: unknown } | { id: string; ok: false; error: string };

/** Unsolicited terminal completion sent to the socket that owns a daemon. */
export interface DaemonCompletionNotification {
	event: "daemon-completed";
	completionId: string;
	owner: string;
	daemon: DaemonSnapshot;
}

export type DaemonWireMessage = DaemonWireResponse | DaemonCompletionNotification;

function isRecord(value: unknown): value is Record<string, unknown> {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}

function record(value: unknown, label: string): Record<string, unknown> {
	if (!isRecord(value)) throw new Error(`${label} must be an object`);
	return value;
}

function stringValue(value: unknown, label: string): string {
	if (typeof value !== "string" || value.length === 0) throw new Error(`${label} must be a non-empty string`);
	return value;
}
function rawString(value: unknown, label: string): string {
	if (typeof value !== "string") throw new Error(`${label} must be a string`);
	return value;
}

function optionalString(value: unknown, label: string): string | undefined {
	if (value === undefined) return undefined;
	return stringValue(value, label);
}

function optionalRawString(value: unknown, label: string): string | undefined {

View on GitHub (pinned to 9690622007)

Solutions

  1. Log the raw payload and label from the error to identify the malformed field
  2. Ensure the sender serializes messages as JSON objects matching the current DaemonWireMessage schema
  3. Align client and broker versions so both sides agree on message shapes

Example fix

// before
socket.write(JSON.stringify("ping"));
// after
socket.write(JSON.stringify({ op: "ping" }));
Defensive patterns

Strategy: type-guard

Validate before calling

function assertPlainObject(v: unknown, label: string): asserts v is Record<string, unknown> {
  if (typeof v !== 'object' || v === null || Array.isArray(v)) throw new Error(`${label} must be an object`);
}

Type guard

function isPlainObject(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null && !Array.isArray(v);
}

Try / catch

try {
  const msg = parseDaemonWireMessage(raw);
} catch (err) {
  if (err.message.endsWith('must be an object')) {
    logger.warn('malformed daemon payload', { raw: String(raw).slice(0, 200) });
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Sending malformed JSON over the broker socket (e.g. a bare string/number/array where a message object is expected); an RPC result or spec field decoded as the wrong type; null entries in payload arrays.

Common situations: Manual JSON-RPC experimentation against the socket; provider/client version skew changing message shapes; serialization bugs writing non-object values into message fields.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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