coleam00/Archon · warning

config must be a JSON-encoded object

Error message

config must be a JSON-encoded object

What it means

On the run-workflow route, the 'config' field must be a JSON-encoded object string. The route rejects it with 400 and this exact message in two cases: the body value is not a string (non-string part type), or JSON.parse of the string throws; the parse failure is logged as run_workflow.config_parse_failed.

Source

Thrown at packages/server/src/routes/api.ts:3468

          return { ok: false, error: `${label} must be a JSON-encoded object` };
        }
      };
      const decodedTiers = decodeObjectField(body.tiers, 'tiers');
      if (!decodedTiers.ok) return apiError(c, 400, decodedTiers.error);
      const decodedAliases = decodeObjectField(body.aliases, 'aliases');
      if (!decodedAliases.ok) return apiError(c, 400, decodedAliases.error);
      const parsedOverrides = parseRunModelOverridesFields(
        decodedTiers.value,
        decodedAliases.value
      );
      if (!parsedOverrides.ok) return apiError(c, 400, parsedOverrides.error);
      workflowModelOverrides = parsedOverrides.overrides;

      if (body.config !== undefined) {
        if (typeof body.config !== 'string') {
          return apiError(c, 400, 'config must be a JSON-encoded object');
        }
        let decodedConfig: unknown;
        try {
          decodedConfig = JSON.parse(body.config) as unknown;
        } catch (parseErr: unknown) {
          getLog().warn({ err: parseErr, workflowName }, 'run_workflow.config_parse_failed');
          return apiError(c, 400, 'config must be a JSON-encoded object');
        }
        try {
          workflowRunConfig = parseWorkflowRunConfig(decodedConfig, {
            kind: 'http',
            label: 'inline',
          });
        } catch (error) {
          return apiError(c, 400, (error as Error).message);
        }
      }

      const rawFiles = body.files;
      const fileList: (string | File)[] = Array.isArray(rawFiles)

View on GitHub (pinned to 0773b97458)

Solutions

  1. Send config as one plain text field with strict JSON: -F 'config={"timeout":300}'.
  2. Validate with JSON.parse locally and lint the JSON (e.g. jq .) before sending.
  3. Check shell quoting — wrap the JSON in single quotes in bash.
  4. Send each config field only once as a string part, never as a file.

Example fix

// before
-F 'config={timeout: 300}'          // invalid JSON
// after
-F 'config={"timeout":300}'         // valid JSON object
Defensive patterns

Strategy: validation

Validate before calling

function encodeConfig(config: Record<string, unknown>): string {
  const json = JSON.stringify(config);
  JSON.parse(json); // round-trip guarantee
  return json;
}
form.append('config', encodeConfig({ timeout: 300 }));

Type guard

function isJsonEncodedConfig(s: unknown): s is string {
  if (typeof s !== 'string' || !s) return false;
  try {
    const v: unknown = JSON.parse(s);
    return typeof v === 'object' && v !== null && !Array.isArray(v);
  } catch { return false; }
}

Try / catch

try {
  const res = await runWorkflow(form);
  if (res.status === 400 && (await res.json()).error === 'config must be a JSON-encoded object') {
    throw new Error('config must be one plain string field holding strict JSON');
  }
} catch (err) { /* re-encode config with JSON.stringify and retry */ }

Prevention

When it happens

Trigger: Sending config as a non-string part (file/array), or as invalid JSON text (trailing comma, single quotes, YAML), when starting a workflow run via multipart.

Common situations: Shell quoting stripping double quotes from config JSON; pasting config with comments or trailing commas; template engines rendering the field empty or with newlines that break the encoder; sending config as a file upload.

Related errors


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