{"record":{"id":"d4473c9ed9fc36c4","repo":"can1357/oh-my-pi","slug":"request-must-be-an-object","errorCode":null,"errorMessage":"request must be an object","messagePattern":"request must be an object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/tui/src/debug-server.ts","lineNumber":283,"sourceCode":"\t\t\twhile (newline !== -1) {\n\t\t\t\tconst line = buffer.slice(0, newline).replace(/\\r$/, \"\");\n\t\t\t\tbuffer = buffer.slice(newline + 1);\n\t\t\t\tif (line.length > 0) this.#handleLine(socket, line);\n\t\t\t\tnewline = buffer.indexOf(\"\\n\");\n\t\t\t}\n\t\t});\n\t\tsocket.on(\"error\", error => {\n\t\t\tvoid error;\n\t\t});\n\t\tsocket.on(\"close\", () => this.#sockets.delete(socket));\n\t}\n\n\t#handleLine(socket: Socket, line: string): void {\n\t\tlet request: DebugRequest;\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(line);\n\t\t\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed))\n\t\t\t\tthrow new Error(\"request must be an object\");\n\t\t\trequest = parsed as DebugRequest;\n\t\t} catch (error) {\n\t\t\tthis.#write(socket, { ok: false, error: `malformed JSON: ${errorMessage(error)}` });\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tconst response = this.#dispatch(request);\n\t\t\tif (request.op === \"quit\" && response.ok) {\n\t\t\t\tthis.#write(socket, response, () => {\n\t\t\t\t\tthis.#tui.stop();\n\t\t\t\t\tprocess.exit(0);\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.#write(socket, response);\n\t\t} catch (error) {\n\t\t\tthis.#write(socket, { ok: false, error: errorMessage(error) });\n\t\t}","sourceCodeStart":265,"sourceCodeEnd":301,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/tui/src/debug-server.ts#L265-L301","documentation":"#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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nsocket.write('[{\"op\":\"keys\",\"keys\":\"a\"}]\\n')\n// after\nsocket.write('{\"op\":\"keys\",\"keys\":\"a\"}\\n')","handlingStrategy":"type-guard","validationCode":"const payload = JSON.stringify({ op: \"keys\", keys: \"ctrl+c\" });\nsocket.write(payload + \"\\n\");","typeGuard":"function isPlainObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  const res = await request(line);\n} catch (err) {\n  if (String(err.message).startsWith(\"malformed JSON\")) {\n    console.error(\"Debug request must be one JSON object per line:\", err.message);\n  }\n}","preventionTips":["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"],"tags":["json","validation","protocol","debug-server"],"backgroundTag":"malformed-json-request","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}