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
- Send one JSON object per line, e.g. {"op":"keys","keys":"ctrl+c"}\n
- Never wrap requests in a top-level array; send one request per line instead
- JSON-encode string payloads before writing to the socket
- 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
- Always serialize with JSON.stringify before writing to the socket
- Send exactly one JSON object per newline-delimited line; never batch in an array
- Check the server's { ok: false, error } response rather than assuming success
- Strip CRLF if your client writes Windows line endings
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
- rpc frame must be an object
- messages must be a list
- Replacement text is not valid UTF-8: {err}
- OMP_AUTH_BROKER_ACCOUNT_POOL_FILE contains an empty provider
- OMP_AUTH_BROKER_ACCOUNT_POOL_FILE contains a provider id wit
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/d4473c9ed9fc36c4.
Report an issue: GitHub.