coleam00/Archon · error
Request body is not valid JSON — send {"decision": "...", "t
Error message
Request body is not valid JSON — send {"decision": "...", "text": "..."} What it means
The server's POST respond endpoint (packages/server/src/routes/api.ts:4061), used to answer a durable wait or interactive node, expects an optional JSON body of shape {decision: string, text: string}. A non-empty body that fails JSON.parse yields this 400; after parsing, a missing decision is validated separately.
Source
Thrown at packages/server/src/routes/api.ts:4061
return apiError(c, 400, `Cannot respond to workflow in '${run.status}' status`);
}
const respondBlocker = pausedGateBlocker(
run,
'Respond on the child run instead, or abandon this run to discard the whole tree.',
false
);
if (respondBlocker) {
return apiError(c, 400, respondBlocker);
}
const rawBody = await c.req.text();
let body: { decision?: string; text?: string } = {};
if (rawBody.trim().length > 0) {
try {
body = JSON.parse(rawBody) as { decision?: string; text?: string };
} catch (parseError) {
getLog().warn({ err: parseError, runId }, 'api.respond_body_parse_failed');
return apiError(
c,
400,
'Request body is not valid JSON — send {"decision": "...", "text": "..."}'
);
}
}
if (!body.decision) {
return apiError(c, 400, 'Request body must include a non-empty "decision"');
}
const decision = body.decision;
// Pre-validate a non-default decision so an undeclared id is a 400 naming the
// gate's actual options, not an opaque 500 — mirrors the approve/reject routes'
// pre-checks above. 'approve'/'reject' skip this: assertRespondable enforces
// decisionsAuthored, which legacy gates (the ones those two ids also serve)
// never set — see assertRespondable's doc comment for why it is not consulted
// for those two ids.
if (decision !== 'approve' && decision !== 'reject') {
try {View on GitHub (pinned to 0773b97458)
Solutions
- Send valid JSON: {"decision": "approve", "text": "..."} with Content-Type: application/json
- Build the payload with JSON.stringify({decision, text}) instead of string concatenation
- Omit the body only if the endpoint's no-body path is intended
- Check the server log keyed 'api.respond_body_parse_failed' for the exact parse error
Example fix
// before
fetch(url, {method:'POST', body:`approve ${notes}`})
// after
fetch(url, {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({decision:'approve', text: notes})}) Defensive patterns
Strategy: validation
Validate before calling
function buildRespondBody(decision, text) {
if (!decision) throw new TypeError('decision is required');
const json = JSON.stringify({ decision, text });
JSON.parse(json);
return json;
} Type guard
function isRespondBody(b: unknown): b is { decision?: string; text?: string } {
return typeof b === 'object' && b !== null &&
(!('decision' in b) || typeof (b as any).decision === 'string') &&
(!('text' in b) || typeof (b as any).text === 'string');
} Try / catch
try {
const res = await fetch(respondUrl, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({decision, text}) });
if (res.status === 400) throw new Error('respond body rejected; check decision/text shape');
} catch (e) { /* log; re-prompt user for the decision */ } Prevention
- Always build the body with JSON.stringify, not template strings
- Validate decision is present before sending (server checks it after parsing)
- Set Content-Type: application/json
- Test payloads containing newlines/quotes locally with JSON.parse
When it happens
Trigger: POSTing a response to a run's wait/gate with a non-empty raw body that is not valid JSON (raw decision word, unescaped quotes in the text, form-encoded or plain-text body).
Common situations: curl -d 'approve' instead of JSON; UI clients stringifying the payload twice or not at all; text containing newlines/quotes pasted into hand-built JSON; proxies rewriting the body.
Related errors
- Request body is not valid JSON — send {"comment": "..."} or
- Request body is not valid JSON — send {"reason": "..."} or n
- Invalid JSON in request body
- Expected directory listing from ${url}, got a single file
- owner returned an invalid response
AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01).
Data as JSON: /api/errors/7ece07eb6f90649d.
Report an issue: GitHub.