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

  1. If reason is `truncated`: re-run/resume with the current binary so full structuredOutput is rehydrated; reduce output size or field count.
  2. If reason is `array-aggregate`: read the array via the aggregate form rather than `$group.field`; the engine fixes array shape at consumption.
  3. 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.
  4. 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

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


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