coleam00/Archon · error
Request body is not valid JSON — send {"reason": "..."} or n
Error message
Request body is not valid JSON — send {"reason": "..."} or no body What it means
The server's POST reject endpoint (packages/server/src/routes/api.ts:3986) accepts an optional JSON body of shape {reason: string}. A non-empty body that fails JSON.parse is rejected with 400 and the message points the caller at the expected shape; it does not treat raw text as the reason.
Source
Thrown at packages/server/src/routes/api.ts:3986
const rejectBlocker = pausedGateBlocker(
run,
'Reject the child run instead, or abandon this run to discard the whole tree.',
false
);
if (rejectBlocker) {
return apiError(c, 400, rejectBlocker);
}
// Mirror of the approve route's malformed-body guard: a swallowed parse
// failure would silently drop the reviewer's reason.
const rawBody = await c.req.text();
let body: { reason?: string } = {};
if (rawBody.trim().length > 0) {
try {
body = JSON.parse(rawBody) as { reason?: string };
} catch (parseError) {
getLog().warn({ err: parseError, runId }, 'api.reject_body_parse_failed');
return apiError(
c,
400,
'Request body is not valid JSON — send {"reason": "..."} or no body'
);
}
}
const reason = body.reason ?? 'Rejected';
// Shared gate logic (events, telemetry, staging/cancel decision). When an
// on_reject rework is staged the run stays 'paused' with
// metadata.approval.resolved = 'rejected' (#2075).
const result = await rejectWorkflow(runId, reason);
if (result.cancelled) {
return c.json({
success: true,
message: result.maxAttemptsReached
? `Workflow rejected and cancelled (max attempts reached): ${run.workflow_name}`
: `Workflow rejected: ${run.workflow_name}`,
});View on GitHub (pinned to 0773b97458)
Solutions
- Send valid JSON: {"reason": "..."} with Content-Type: application/json
- Omit the body — the endpoint supplies the default reason 'Rejected'
- Check the server log keyed 'api.reject_body_parse_failed' for the exact parse error
- Validate the payload locally with JSON.parse before sending
Example fix
// before
curl -X POST $URL/runs/$RUN/reject -d 'timing issue'
// after
curl -X POST $URL/runs/$RUN/reject -H 'Content-Type: application/json' -d '{"reason":"timing issue"}' Defensive patterns
Strategy: validation
Validate before calling
function buildRejectBody(reason) {
if (reason === undefined) return null; // server defaults to 'Rejected'
const json = JSON.stringify({ reason });
JSON.parse(json);
return json;
} Type guard
function isRejectBody(b: unknown): b is { reason?: string } {
return typeof b === 'object' && b !== null &&
(!('reason' in b) || typeof (b as any).reason === 'string');
} Try / catch
try {
const res = await fetch(rejectUrl, { method:'POST', headers:{'Content-Type':'application/json'}, body: bodyJson ?? undefined });
if (!res.ok) throw new Error(`reject failed: ${res.status}`);
} catch (e) { /* surface to operator; do not silently retry */ } Prevention
- Serialize with JSON.stringify so quotes/newlines in the reason are escaped
- Never pass raw prose as a request body
- Omit the body to use the default 'Rejected' reason
- Check for smart quotes when copying JSON from docs
When it happens
Trigger: POSTing to the run-reject route with a non-empty raw body that is not valid JSON (bare text reason, trailing commas, unescaped quotes, HTML body). Empty body is allowed and defaults reason to 'Rejected'.
Common situations: curl -d 'timing issue' without JSON formatting; webhook/automation tools sending form-encoded bodies; quotes inside the reason string breaking the JSON envelope; copy-pasted JSON with smart quotes.
Related errors
- Request body is not valid JSON — send {"comment": "..."} or
- Request body is not valid JSON — send {"decision": "...", "t
- 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/eb5ef0b205ea5b4c.
Report an issue: GitHub.