Hmbown/CodeWhale · error · RuntimeError

Codewhale stream-json did not end with exactly one done even

Error message

Codewhale stream-json did not end with exactly one done event

What it means

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.

Source

Thrown at integrations/verifiers-codewhale/codewhale_harness/harness.py:382

        ) != STREAM_SCHEMA_VERSION:
            raise RuntimeError("Codewhale stream-json schema did not match v0.9.1")
        event_type = event.get("type")
        if event_type not in _EVENT_TYPES:
            raise RuntimeError("Codewhale stream-json contained an unknown event type")
        counts[event_type] += 1
        ordered_types.append(event_type)
        if event_type == "metadata":
            if terminal is not None:
                raise RuntimeError("Codewhale emitted more than one terminal metadata receipt")
            meta = event.get("meta")
            if not isinstance(meta, dict) or meta.get("receipt_kind") != "terminal":
                raise RuntimeError("Codewhale metadata event was not a terminal receipt")
            terminal = _bounded_terminal(meta)

    if terminal is None:
        raise RuntimeError("Codewhale stream-json omitted terminal metadata")
    if counts["done"] != 1 or not ordered_types or ordered_types[-1] != "done":
        raise RuntimeError("Codewhale stream-json did not end with exactly one done event")
    if ordered_types[-2:-1] != ["metadata"]:
        raise RuntimeError("Codewhale terminal metadata did not immediately precede done")
    for field in ["binary_sha256", "prompt_sha256"]:
        if not isinstance(terminal.get(field), str) or not _SHA256.fullmatch(
            terminal[field]
        ):
            raise RuntimeError(f"Codewhale terminal receipt omitted a valid {field}")
    return {
        "schema": STREAM_SCHEMA,
        "schema_version": STREAM_SCHEMA_VERSION,
        "events": dict(sorted(counts.items())),
        "terminal": terminal,
    }

View on GitHub (pinned to 8880682c63)

Solutions

  1. List ordered event types from captured stdout: verify the sequence ends with [..., 'metadata', 'done'] and contains exactly one 'done'.
  2. Remove any events emitted after done — done must be the final stdout line.
  3. Fix retry logic so only the final attempt emits done.
  4. Pin to Codewhale 0.9.1 or update the harness contract in lockstep with the facade changes.

Example fix

# before: wrapper appends a summary event after done
emit_done()
sys.stdout.write(json.dumps({"type": "content", "text": "summary"}) + "\n")

# after: done stays last
sys.stdout.write(json.dumps(summary_line) + "\n", file=sys.stderr)  # or emit before done
emit_done()
Defensive patterns

Strategy: validation

Validate before calling

def stream_ends_with_single_done(stdout: str) -> bool:
    types = [
        json.loads(line).get("type")
        for line in stdout.splitlines()
        if line.strip()
    ]
    return types.count("done") == 1 and bool(types) and types[-1] == "done"

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/a4c746b66d9fc583. Report an issue: GitHub.