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

  1. Send valid JSON: {"decision": "approve", "text": "..."} with Content-Type: application/json
  2. Build the payload with JSON.stringify({decision, text}) instead of string concatenation
  3. Omit the body only if the endpoint's no-body path is intended
  4. 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

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


AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01). Data as JSON: /api/errors/7ece07eb6f90649d. Report an issue: GitHub.