{"record":{"id":"190911ba375e9342","repo":"coleam00/Archon","slug":"request-body-is-not-valid-json-send-comment","errorCode":null,"errorMessage":"Request body is not valid JSON — send {\"comment\": \"...\"} or no body","messagePattern":"Request body is not valid JSON — send (.+?) or no body","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"packages/server/src/routes/api.ts","lineNumber":3921,"sourceCode":"        'Approve or reject the child run instead.',\n        true\n      );\n      if (approveBlocker) {\n        return apiError(c, 400, approveBlocker);\n      }\n      // Distinguish \"no body sent\" (legitimate bare approve) from \"body sent but\n      // unparseable\" (client bug). Since #2074 a bare approve FINALIZES a\n      // signal-bearing loop gate, so silently coercing a malformed body to {}\n      // would discard intended feedback and finalize undiagnosed — reject it.\n      const rawBody = await c.req.text();\n      let body: { comment?: string } = {};\n      if (rawBody.trim().length > 0) {\n        try {\n          body = JSON.parse(rawBody) as { comment?: string };\n        } catch (parseError) {\n          getLog().warn({ err: parseError, runId }, 'api.approve_body_parse_failed');\n          return apiError(\n            c,\n            400,\n            'Request body is not valid JSON — send {\"comment\": \"...\"} or no body'\n          );\n        }\n      }\n      // Shared gate logic (events, telemetry, metadata staging) — the run stays\n      // 'paused' with metadata.approval.resolved = 'approved' (#2075). The\n      // pre-checks above map the common error cases to 400s; approveWorkflow\n      // re-validates and anything it throws past them is a 500.\n      // The raw (possibly undefined) comment is passed through — approveWorkflow\n      // defaults the recorded comment internally, but \"no feedback\" must survive\n      // so a signal-bearing interactive-loop gate finalizes instead of re-running\n      // (#2074, loop_feedback_given).\n      await approveWorkflow(runId, body.comment);\n\n      // Auto-resume: dispatch to the orchestrator so the workflow continues\n      // without requiring the user to re-run the workflow command. Mirrors\n      // what `workflowApproveCommand` does in the CLI. Requires","sourceCodeStart":3903,"sourceCodeEnd":3939,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/server/src/routes/api.ts#L3903-L3939","documentation":"The server's POST approve endpoint (packages/server/src/routes/api.ts:3921) accepts an optional JSON body of shape {comment: string}. When a request sends a non-empty body that JSON.parse cannot parse, it rejects with 400 instead of guessing at malformed input, keeping the approval-gate API strict and auditable.","triggerScenarios":"POSTing to the run-approve route with a non-empty raw body that is not valid JSON (e.g. a bare comment string like \"looks good\", trailing commas, single quotes, or an HTML/plain-text body from curl without a Content-Type set). An empty body is allowed.","commonSituations":"curl -d 'approved' without quoting JSON; clients setting text/plain or form bodies; templated shell commands where the comment contains unescaped quotes that break JSON; automation scripts that pass a raw message as the body.","solutions":["Fix the client to send valid JSON: {\"comment\": \"...\"} with Content-Type: application/json","Omit the request body entirely — the endpoint allows no body","Inspect the server log keyed 'api.approve_body_parse_failed' for the exact parse error","Use a JSON-aware client flag, e.g. curl -H 'Content-Type: application/json' -d '{\"comment\":\"ok\"}'"],"exampleFix":"// before\ncurl -X POST $URL/runs/$RUN/approve -d 'ship it'\n// after\ncurl -X POST $URL/runs/$RUN/approve -H 'Content-Type: application/json' -d '{\"comment\":\"ship it\"}'","handlingStrategy":"validation","validationCode":"function buildApproveBody(comment) {\n  if (comment === undefined) return null; // omit body entirely\n  const json = JSON.stringify({ comment });\n  JSON.parse(json); // prove serializable before sending\n  return json;\n}","typeGuard":"function isApproveBody(b: unknown): b is { comment?: string } {\n  return typeof b === 'object' && b !== null &&\n    !('comment' in b) || typeof (b as any).comment === 'string';\n}","tryCatchPattern":"try {\n  const res = await fetch(url, { method:'POST', headers:{'Content-Type':'application/json'}, body: bodyJson ?? undefined });\n  if (res.status === 400) throw new Error('approve body rejected as invalid JSON');\n} catch (e) { /* log and fall back to no-body approve */ }","preventionTips":["Always send bodies with JSON.stringify, never string concatenation","Set Content-Type: application/json on POSTs","Omit the body when there is no comment","Validate payloads with JSON.parse locally before sending"],"tags":["http","json","api","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"}