can1357/oh-my-pi · error

request must be an object

Error message

request must be an object

What it means

#handleLine parses each NDJSON line as JSON and requires the result to be a plain JSON object. Arrays, strings, numbers, booleans, null, or unparseable text are rejected with this error (or the JSON.parse error), and a { ok: false, error: "malformed JSON: ..." } response is written back to the socket. This is an input-shape guard on the debug protocol boundary.

Source

Thrown at packages/tui/src/debug-server.ts:283

			while (newline !== -1) {
				const line = buffer.slice(0, newline).replace(/\r$/, "");
				buffer = buffer.slice(newline + 1);
				if (line.length > 0) this.#handleLine(socket, line);
				newline = buffer.indexOf("\n");
			}
		});
		socket.on("error", error => {
			void error;
		});
		socket.on("close", () => this.#sockets.delete(socket));
	}

	#handleLine(socket: Socket, line: string): void {
		let request: DebugRequest;
		try {
			const parsed: unknown = JSON.parse(line);
			if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
				throw new Error("request must be an object");
			request = parsed as DebugRequest;
		} catch (error) {
			this.#write(socket, { ok: false, error: `malformed JSON: ${errorMessage(error)}` });
			return;
		}
		try {
			const response = this.#dispatch(request);
			if (request.op === "quit" && response.ok) {
				this.#write(socket, response, () => {
					this.#tui.stop();
					process.exit(0);
				});
				return;
			}
			this.#write(socket, response);
		} catch (error) {
			this.#write(socket, { ok: false, error: errorMessage(error) });
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Send one JSON object per line, e.g. {"op":"keys","keys":"ctrl+c"}\n
  2. Never wrap requests in a top-level array; send one request per line instead
  3. JSON-encode string payloads before writing to the socket
  4. Inspect the returned error field — it echoes the underlying parse/validation failure

Example fix

// before
socket.write('[{"op":"keys","keys":"a"}]\n')
// after
socket.write('{"op":"keys","keys":"a"}\n')
Defensive patterns

Strategy: type-guard

Validate before calling

const payload = JSON.stringify({ op: "keys", keys: "ctrl+c" });
socket.write(payload + "\n");

Type guard

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

Try / catch

try {
  const res = await request(line);
} catch (err) {
  if (String(err.message).startsWith("malformed JSON")) {
    console.error("Debug request must be one JSON object per line:", err.message);
  }
}

Prevention

When it happens

Trigger: Sending a line over the OMP_TUI_DEBUG socket whose JSON.parse result is an array (e.g. "[]"), a scalar (e.g. "42", "\"keys\""), null, or any non-JSON text.

Common situations: Piping raw text into the socket without JSON-encoding it; wrapping requests in an array for batch sending; trailing newline/CRLF issues producing empty or partial lines; curl/netcat usage without quoting.

Related errors


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