{"record":{"id":"434554c877397da6","repo":"coleam00/Archon","slug":"invalid-json-in-request-body","errorCode":null,"errorMessage":"Invalid JSON in request body","messagePattern":"Invalid JSON in request body","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"packages/server/src/routes/api.ts","lineNumber":2928,"sourceCode":"      }\n\n      const fileEntries = fileList.filter((e): e is File => e instanceof File);\n      if (fileEntries.length > 0) {\n        const result = await persistUploadedFiles(conversationId, fileEntries);\n        if (!result.ok) {\n          return c.json({ error: result.error }, result.status);\n        }\n        savedFiles = result.savedFiles;\n        uploadDir = result.uploadDir;\n        getLog().info({ conversationId, fileCount: savedFiles.length }, 'message.files_uploaded');\n      }\n    } else {\n      let body: { message?: unknown };\n      try {\n        body = await c.req.json();\n      } catch (parseErr: unknown) {\n        getLog().warn({ err: parseErr, conversationId }, 'message.json_parse_failed');\n        return c.json({ error: 'Invalid JSON in request body' }, 400);\n      }\n\n      if (typeof body.message !== 'string' || !body.message) {\n        return c.json({ error: 'message must be a non-empty string' }, 400);\n      }\n      message = body.message;\n    }\n\n    // Look up conversation for message persistence\n    let conv: Awaited<ReturnType<typeof conversationDb.findConversationByPlatformId>> = null;\n    try {\n      conv = await conversationDb.findConversationByPlatformId(conversationId);\n    } catch (e: unknown) {\n      getLog().error({ err: e, conversationId }, 'conversation_lookup_failed');\n    }\n\n    // Persist user message and pass DB ID to adapter for assistant message persistence\n    if (conv) {","sourceCodeStart":2910,"sourceCodeEnd":2946,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/server/src/routes/api.ts#L2910-L2946","documentation":"The conversation-message route expected a JSON body ({ message: string }) but c.req.json() threw because the body is not valid JSON. The server logs message.json_parse_failed with the parse error and conversationId, then returns 400 with this message, indicating a malformed client request rather than a server fault.","triggerScenarios":"POSTing to the conversation message endpoint with Content-Type application/json but a body that is empty, truncated, or invalid JSON (unquoted keys, trailing commas, plain text, binary data).","commonSituations":"Client sending form-encoded or raw-text body while claiming JSON content type; a proxy/CDN truncating the body; string-template code building JSON without escaping; forgetting JSON.stringify on the payload.","solutions":["JSON.stringify the payload object and send it as the body with Content-Type: application/json.","Validate the body parses locally (JSON.parse in a test) before sending.","Check for body truncation through proxies/timeouts on large messages.","If the payload includes files, switch to the multipart variant of the route instead of embedding binary data in JSON."],"exampleFix":"// before\nawait fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: `{ message: \"${msg}\" }` });\n// after\nawait fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: msg }) });","handlingStrategy":"validation","validationCode":"function buildMessageBody(message: string): string {\n  if (typeof message !== 'string' || !message) throw new Error('message must be a non-empty string');\n  return JSON.stringify({ message }); // validate locally what the route expects\n}\nconst body = buildMessageBody(msg); // JSON.parse(body) round-trips safely","typeGuard":"function isValidMessageBody(raw: string): boolean {\n  try {\n    const parsed: unknown = JSON.parse(raw);\n    return typeof (parsed as { message?: unknown })?.message === 'string' && (parsed as { message: string }).message.length > 0;\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  const res = await sendMessage(body);\n  if (res.status === 400) {\n    const { error } = await res.json();\n    if (error === 'Invalid JSON in request body') throw new Error('Client sent non-JSON body; check JSON.stringify/content-type');\n  }\n} catch (err) { /* surface client-side serialization bug */ }","preventionTips":["Always JSON.stringify request objects; never interpolate strings into JSON templates.","Send Content-Type: application/json together with a stringified body.","Validate the payload with JSON.parse in tests before shipping the client call.","Use the multipart route for file payloads instead of embedding binary in JSON."],"tags":["http-400","json","request-validation","client-error"],"backgroundTag":"invalid-json-request-body","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}