{"record":{"id":"24a430a009937910","repo":"Hmbown/CodeWhale","slug":"codewhale-stream-json-omitted-terminal-metadata","errorCode":null,"errorMessage":"Codewhale stream-json omitted terminal metadata","messagePattern":"Codewhale stream-json omitted terminal metadata","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"integrations/verifiers-codewhale/codewhale_harness/harness.py","lineNumber":380,"sourceCode":"        if event.get(\"schema\") != STREAM_SCHEMA or event.get(\n            \"schema_version\"\n        ) != STREAM_SCHEMA_VERSION:\n            raise RuntimeError(\"Codewhale stream-json schema did not match v0.9.1\")\n        event_type = event.get(\"type\")\n        if event_type not in _EVENT_TYPES:\n            raise RuntimeError(\"Codewhale stream-json contained an unknown event type\")\n        counts[event_type] += 1\n        ordered_types.append(event_type)\n        if event_type == \"metadata\":\n            if terminal is not None:\n                raise RuntimeError(\"Codewhale emitted more than one terminal metadata receipt\")\n            meta = event.get(\"meta\")\n            if not isinstance(meta, dict) or meta.get(\"receipt_kind\") != \"terminal\":\n                raise RuntimeError(\"Codewhale metadata event was not a terminal receipt\")\n            terminal = _bounded_terminal(meta)\n\n    if terminal is None:\n        raise RuntimeError(\"Codewhale stream-json omitted terminal metadata\")\n    if counts[\"done\"] != 1 or not ordered_types or ordered_types[-1] != \"done\":\n        raise RuntimeError(\"Codewhale stream-json did not end with exactly one done event\")\n    if ordered_types[-2:-1] != [\"metadata\"]:\n        raise RuntimeError(\"Codewhale terminal metadata did not immediately precede done\")\n    for field in [\"binary_sha256\", \"prompt_sha256\"]:\n        if not isinstance(terminal.get(field), str) or not _SHA256.fullmatch(\n            terminal[field]\n        ):\n            raise RuntimeError(f\"Codewhale terminal receipt omitted a valid {field}\")\n    return {\n        \"schema\": STREAM_SCHEMA,\n        \"schema_version\": STREAM_SCHEMA_VERSION,\n        \"events\": dict(sorted(counts.items())),\n        \"terminal\": terminal,\n    }\n","sourceCodeStart":362,"sourceCodeEnd":396,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/integrations/verifiers-codewhale/codewhale_harness/harness.py#L362-L396","documentation":"After consuming the whole stream, the harness requires that it captured a terminal metadata receipt. This error means no metadata event with a valid terminal meta ever appeared — the stream ended (or broke) without the one receipt that carries usage, hashes, and status the harness must retain.","triggerScenarios":"Codewhale crashed or was killed before emitting its terminal metadata; the binary emitted only content/tool events and then a done (or nothing); the process wrote the receipt to stderr instead of stdout; truncation cut stdout before the final metadata line.","commonSituations":"Rollouts hitting runtime timeouts or OOM kills mid-run; binary_path wrappers redirecting the last lines incorrectly; a Codewhale version that emits the receipt only on success and skips it on error paths; excessive stdout buffering losing the tail.","solutions":["Check the rollout's process exit status and stderr — a non-zero exit or crash explains the missing terminal receipt.","Confirm the facade always emits terminal metadata immediately before done, including on error paths (status/error_category exist precisely for that).","Increase runtime/timeout limits if the process was killed mid-run.","Capture full stdout to a file and verify the last two non-blank lines are the metadata event then the done event."],"exampleFix":"# before: error path exits without a receipt\nexcept Exception:\n    sys.exit(1)\n\n# after: error path still emits terminal metadata + done\nexcept Exception as exc:\n    emit_metadata(status=\"error\", error_category=type(exc).__name__)\n    emit_done()","handlingStrategy":"try-catch","validationCode":"def stream_has_terminal(stdout: str) -> bool:\n    return any(\n        isinstance(e.get(\"meta\"), dict) and e[\"meta\"].get(\"receipt_kind\") == \"terminal\"\n        for e in map(json.loads, filter(str.strip, stdout.splitlines()))\n        if isinstance(e, dict) and e.get(\"type\") == \"metadata\"\n    )","typeGuard":null,"tryCatchPattern":"try:\n    receipt = _parse_stream_receipt(result.stdout)\nexcept RuntimeError as error:\n    if \"omitted terminal metadata\" in str(error):\n        logger.error(\n            \"codewhale exit=%s stderr=%s — receipt lost before finalization\",\n            result.returncode, result.stderr,\n        )\n    raise","preventionTips":["Ensure the facade emits terminal metadata on every exit path, including errors and exceptions.","Watch the process exit code: non-zero exits are the usual reason the receipt never arrives.","Avoid stdout buffering traps — flush before process exit or use line-buffered writes."],"tags":["metadata","stream-json","codewhale-harness","truncation","process-crash"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}