{"record":{"id":"ddb9288ae3487e87","repo":"coleam00/Archon","slug":"missing-key","errorCode":"missing-key","errorMessage":"missing-key","messagePattern":"missing-key","errorType":"error_code","errorClass":"OutputRefError","httpStatus":null,"severity":"error","filePath":"packages/workflows/src/output-ref.ts","lineNumber":401,"sourceCode":"\n  // 2. Structured payload without a declared schema (legacy rows / non-object\n  //    schema): prefer it, but stay lenient — with no schema we cannot tell an\n  //    optional-absent field from a typo, so an absent field is '' (not a throw).\n  //    A present null value is kept (callers stringify it to \"null\"), matching\n  //    the historical structuredOutput-preference behavior.\n  if (structuredObj !== undefined) {\n    const value = structuredObj[field];\n    if (value === undefined) return { kind: 'empty' };\n    return { kind: 'value', value };\n  }\n\n  // 3. Schemaless producer (bash/script/prose). The author wrote `.field`, so\n  //    JSON carrying that key is expected; anything else is a drop they must see.\n  const obj = parseOutputObject(nodeOutput.output);\n  if (obj === undefined) {\n    throw new OutputRefError(nodeId, field, unparseableReason(nodeOutput.output));\n  }\n  if (!(field in obj)) throw new OutputRefError(nodeId, field, 'missing-key');\n  return { kind: 'value', value: obj[field] };\n}\n\n/**\n * Guard the whole-text `$node.output` form against a failed producer's stale output\n * (#2696/#2710/#2713): a `loop_group`'s failure paths carry the last completed\n * iteration's real, often-valid-JSON output text, which must never be read as if the\n * producer had succeeded. Mirrors the `state === 'failed'` guard already built into\n * `resolveNodeOutputField` above for the fielded form, so every whole-text reader\n * routes through this one function instead of repeating the check (#2722), replacing\n * the KEEP-IN-SYNC enumeration this module doc used to carry. This is a runtime check,\n * not a type-level one — nothing stops a future caller from reading `nodeOutput.output`\n * directly without calling this function first; the value is having one place to route\n * through, not a compiler-enforced guarantee against bypass.\n *\n * `buildMessage` lets each caller keep its own wording — a binding directive names\n * `if_skipped`, a `when:` guard names the condition, and so on — only the\n * check-and-throw mechanism is shared.","sourceCodeStart":383,"sourceCodeEnd":419,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/workflows/src/output-ref.ts#L383-L419","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Fix the producer so every code path emits the referenced key (with a default if needed).","Add a shell/templating fallback at the consumer: `${producer.field:-default}` style handling or an explicit default binding.","Declare the field in the producer's `output_format` (optional) so it is validated as consistently present-or-explicitly-null.","Rename the consumer reference to the key the producer actually emits (log the output to check)."],"exampleFix":"// before\nbash: |\n  if ok; then jq -n '{summary: $s}'; fi\n// after\nbash: |\n  if ok; then s=$val; else s='n/a'; fi\n  jq -n --arg s \"$s\" '{summary: $s}'","handlingStrategy":"fallback","validationCode":"const obj = parseOutputObject(nodeOutput.output);\nif (obj !== undefined && !(field in obj)) {\n  console.warn(`Producer ${nodeId} output lacks key '${field}'; keys: ${Object.keys(obj).join(', ')}`);\n}","typeGuard":"function outputHasKey(text: string, field: string): boolean {\n  const obj = parseOutputObject(text);\n  return obj !== undefined && field in obj;\n}","tryCatchPattern":"try {\n  const { value } = resolveNodeOutputField(nodeOutput, nodeId, field);\n} catch (err) {\n  if (err instanceof OutputRefError && err.reason === 'missing-key') {\n    log.warn(`$${nodeId}.${field} missing; using default`);\n    return defaultFor(field);\n  }\n  throw err;\n}","preventionTips":["Make producer scripts emit every referenced key on every path, with explicit defaults.","Declare fields in output_format (required or optional) so presence is validated at the producer.","After changing a producer's JSON shape, grep consumers for `$producer.` refs and update them.","Prefer `${producer.field:-default}`-style explicit fallbacks for optional fields."],"tags":["workflow","output-refs","missing-key"],"backgroundTag":"missing-output-field","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}