coleam00/Archon · error · OutputRefError
unparseableReason(nodeOutput.output) (dynamic reason code fo
Error message
unparseableReason(nodeOutput.output) (dynamic reason code for unparseable producer output)
What it means
For a declared-schema producer, if the output exists in the declared schema but cannot be parsed into a JSON object, the engine throws an OutputRefError whose reason comes from unparseableReason(): `unparseable`, `truncated` (a truncation marker indicates clipped persisted output), or `array-aggregate` (a fan-out aggregate that is a JSON array, not an object). Declaring a schema must fail at least as loudly as the schemaless path (#2456) — returning empty would make declared fields silently become ''.
Source
Thrown at packages/workflows/src/output-ref.ts:375
// 1. Declared-schema producer — the declared property set IS the contract.
if (declaredFields !== undefined) {
if (!declaredFields.includes(field)) {
throw new OutputRefError(nodeId, field, 'not-in-schema');
}
// Prefer the parsed payload; fall back to parsing the JSON-serialized output.
// The fallback covers older NodeOutput rows that predate `structuredOutput`,
// and resumes of runs persisted before `structured_output` rode along in
// `node_completed` events (#2637) — current resumes rehydrate the payload.
const obj = structuredObj ?? parseOutputObject(nodeOutput.output);
// No parseable object AT ALL is not a declared-optional field — it is a producer
// that did not honour its schema, and it must fail exactly as loudly as the
// schemaless path below (#2456). Returning empty here made declaring
// `output_format` QUIETER than declaring nothing, which is backwards: a
// `workflow:` node's output_format is never validated against the child (it only
// populates declaredFields), so every declared field silently became ''.
if (obj === undefined) {
throw new OutputRefError(nodeId, field, unparseableReason(nodeOutput.output));
}
const value = obj[field];
// Required fields are guaranteed present (the producer validated post-parse),
// so a missing/explicit-null value here is a declared-optional field → empty.
if (value === undefined || value === null) return { kind: 'empty' };
return { kind: 'value', value };
}
// 2. Structured payload without a declared schema (legacy rows / non-object
// schema): prefer it, but stay lenient — with no schema we cannot tell an
// optional-absent field from a typo, so an absent field is '' (not a throw).
// A present null value is kept (callers stringify it to "null"), matching
// the historical structuredOutput-preference behavior.
if (structuredObj !== undefined) {
const value = structuredObj[field];
if (value === undefined) return { kind: 'empty' };
return { kind: 'value', value };
}View on GitHub (pinned to 0773b97458)
Solutions
- If reason is `truncated`: re-run/resume with the current binary so full structuredOutput is rehydrated; reduce output size or field count.
- If reason is `array-aggregate`: read the array via the aggregate form rather than `$group.field`; the engine fixes array shape at consumption.
- If reason is `unparseable`: strengthen the producer prompt/script to emit a JSON object matching output_format, or add a post-process step that coerces output to JSON.
- Inspect the producer's raw output in the run to see what was actually emitted.
Example fix
// before (producer emits prose)
prompt: Summarize the diff.
// after
prompt: |
Summarize the diff.
Respond ONLY with JSON matching: {"summary": string} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-validate the stored output before resolving fields
const parsed = parseOutputObject(nodeOutput.output);
if (parsed === undefined && nodeOutput.structuredOutput === undefined) {
console.warn(`Producer ${nodeId} output is not a JSON object:`, unparseableReason(nodeOutput.output));
} Type guard
function producerOutputIsObject(o: { output: string; structuredOutput?: unknown }): boolean {
return asPlainObject(o.structuredOutput) !== undefined || parseOutputObject(o.output) !== undefined;
} Try / catch
try {
const { value } = resolveNodeOutputField(nodeOutput, nodeId, field);
} catch (err) {
if (err instanceof OutputRefError && ['unparseable','truncated','array-aggregate'].includes(err.reason)) {
if (err.reason === 'truncated') return rehydrateOrResume();
if (err.reason === 'array-aggregate') return consumeAggregateForm();
return repairOrReRunProducer(nodeId);
}
throw err;
} Prevention
- Pair output_format with prompts that say 'respond ONLY with JSON matching <schema>'.
- Post-process AI output to strip code fences before it is persisted.
- Keep node outputs under persistence size limits to avoid truncation markers.
- For aggregates (loop_group), use the aggregate consumption form, not `$group.field`.
When it happens
Trigger: A declared-fields producer (its structuredOutput is absent/not an object, so the engine falls back to parsing `nodeOutput.output`) emitted text that is not a JSON object: prose, fenced non-JSON, clipped/truncated JSON on resume, or a JSON array from a fan-out aggregate.
Common situations: AI node ignoring output_format and returning prose; output too large and truncated before persistence, then read on resume; a loop_group/fan-out aggregate stored as an array while a consumer reads `.field` from it; model wrapping JSON in code fences the fence-stripper cannot parse.
Related errors
- producer-not-run
- producer-failed
- not-in-schema
- missing-key
- Cannot execute run '${detachedPreCreatedRun.id}': it belongs
AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01).
Data as JSON: /api/errors/6d6fe47c756c9527.
Report an issue: GitHub.