coleam00/Archon · error · OutputRefError

missing-key

missing-key

Error message

missing-key

What it means

On the schemaless path, the producer's output parsed into a JSON object but does not contain the referenced key, so `$node.field` has no value. The engine throws rather than silently substituting empty, because a missing key usually means the producer dropped the field the author expects.

Source

Thrown at packages/workflows/src/output-ref.ts:401

  // 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 };
  }

  // 3. Schemaless producer (bash/script/prose). The author wrote `.field`, so
  //    JSON carrying that key is expected; anything else is a drop they must see.
  const obj = parseOutputObject(nodeOutput.output);
  if (obj === undefined) {
    throw new OutputRefError(nodeId, field, unparseableReason(nodeOutput.output));
  }
  if (!(field in obj)) throw new OutputRefError(nodeId, field, 'missing-key');
  return { kind: 'value', value: obj[field] };
}

/**
 * Guard the whole-text `$node.output` form against a failed producer's stale output
 * (#2696/#2710/#2713): a `loop_group`'s failure paths carry the last completed
 * iteration's real, often-valid-JSON output text, which must never be read as if the
 * producer had succeeded. Mirrors the `state === 'failed'` guard already built into
 * `resolveNodeOutputField` above for the fielded form, so every whole-text reader
 * routes through this one function instead of repeating the check (#2722), replacing
 * the KEEP-IN-SYNC enumeration this module doc used to carry. This is a runtime check,
 * not a type-level one — nothing stops a future caller from reading `nodeOutput.output`
 * directly without calling this function first; the value is having one place to route
 * through, not a compiler-enforced guarantee against bypass.
 *
 * `buildMessage` lets each caller keep its own wording — a binding directive names
 * `if_skipped`, a `when:` guard names the condition, and so on — only the
 * check-and-throw mechanism is shared.

View on GitHub (pinned to 0773b97458)

Solutions

  1. Fix the producer so every code path emits the referenced key (with a default if needed).
  2. Add a shell/templating fallback at the consumer: `${producer.field:-default}` style handling or an explicit default binding.
  3. Declare the field in the producer's `output_format` (optional) so it is validated as consistently present-or-explicitly-null.
  4. Rename the consumer reference to the key the producer actually emits (log the output to check).

Example fix

// before
bash: |
  if ok; then jq -n '{summary: $s}'; fi
// after
bash: |
  if ok; then s=$val; else s='n/a'; fi
  jq -n --arg s "$s" '{summary: $s}'
Defensive patterns

Strategy: fallback

Validate before calling

const obj = parseOutputObject(nodeOutput.output);
if (obj !== undefined && !(field in obj)) {
  console.warn(`Producer ${nodeId} output lacks key '${field}'; keys: ${Object.keys(obj).join(', ')}`);
}

Type guard

function outputHasKey(text: string, field: string): boolean {
  const obj = parseOutputObject(text);
  return obj !== undefined && field in obj;
}

Try / catch

try {
  const { value } = resolveNodeOutputField(nodeOutput, nodeId, field);
} catch (err) {
  if (err instanceof OutputRefError && err.reason === 'missing-key') {
    log.warn(`$${nodeId}.${field} missing; using default`);
    return defaultFor(field);
  }
  throw err;
}

Prevention

When it happens

Trigger: Consumer references `$producer.field` and the producer's parsed JSON object lacks `field` — the script omitted the key on some code path, an AI node returned a differently-shaped object, or an optional field was never populated.

Common situations: Bash script whose success path prints the key but an early-exit path does not; AI model omitting an optional key despite the prompt; producer schema changed while a consumer still reads an old key name.

Related errors


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