{"record":{"id":"a4c746b66d9fc583","repo":"Hmbown/CodeWhale","slug":"codewhale-stream-json-did-not-end-with-exactly-one","errorCode":null,"errorMessage":"Codewhale stream-json did not end with exactly one done event","messagePattern":"Codewhale stream-json did not end with exactly one done event","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"integrations/verifiers-codewhale/codewhale_harness/harness.py","lineNumber":382,"sourceCode":"        ) != 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":364,"sourceCodeEnd":396,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/integrations/verifiers-codewhale/codewhale_harness/harness.py#L364-L396","documentation":"The stream must terminate with exactly one done event and nothing after it: the harness checks counts['done'] == 1 and that the last entry in ordered_types is 'done'. This error fires when done is missing, emitted more than once, or when other events (even blank-skipped trailing noise parsed as events) follow it.","triggerScenarios":"Zero done events (crash before completion), two done events (retry loops re-running the finalization), or events after done (a wrapper printing a summary line that parses as a valid event).","commonSituations":"Facades that wrap Codewhale and then print their own trailing event; binaries that emit done once per attempt; stdout fixtures where extra lines were appended after the terminator.","solutions":["List ordered event types from captured stdout: verify the sequence ends with [..., 'metadata', 'done'] and contains exactly one 'done'.","Remove any events emitted after done — done must be the final stdout line.","Fix retry logic so only the final attempt emits done.","Pin to Codewhale 0.9.1 or update the harness contract in lockstep with the facade changes."],"exampleFix":"# before: wrapper appends a summary event after done\nemit_done()\nsys.stdout.write(json.dumps({\"type\": \"content\", \"text\": \"summary\"}) + \"\\n\")\n\n# after: done stays last\nsys.stdout.write(json.dumps(summary_line) + \"\\n\", file=sys.stderr)  # or emit before done\nemit_done()","handlingStrategy":"validation","validationCode":"def stream_ends_with_single_done(stdout: str) -> bool:\n    types = [\n        json.loads(line).get(\"type\")\n        for line in stdout.splitlines()\n        if line.strip()\n    ]\n    return types.count(\"done\") == 1 and bool(types) and types[-1] == \"done\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Emit done exactly once, as the final stdout line, on success and failure paths alike.","Never print events or wrappers after done.","In retry loops, emit done only for the last attempt."],"tags":["stream-json","validation","codewhale-harness","ordering"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}