{"record":{"id":"5563c8da30b6a2a3","repo":"rohitg00/ai-engineering-from-scratch","slug":"event-arrived-after-message-stop","errorCode":null,"errorMessage":"event arrived after message_stop","messagePattern":"event arrived after message_stop","errorType":"exception","errorClass":"ProtocolError","httpStatus":null,"severity":"error","filePath":"certifications/claude/lessons/08-messages-api-and-application-lifecycle/code/main.py","lineNumber":166,"sourceCode":"    if not isinstance(blocks, list) or not blocks:\n        raise ProtocolError(\"response content must be a non-empty block list\")\n    if not all(isinstance(block, dict) and isinstance(block.get(\"type\"), str) for block in blocks):\n        raise ProtocolError(\"every content block needs a type\")\n    return blocks\n\n\ndef _text_from_blocks(blocks: list[dict[str, Any]]) -> str:\n    return \"\".join(str(block.get(\"text\", \"\")) for block in blocks if block[\"type\"] == \"text\")\n\n\ndef collect_stream_text(events: Iterable[dict[str, Any]]) -> str:\n    \"\"\"Collect only text deltas while checking that a stream terminates.\"\"\"\n    chunks: list[str] = []\n    stopped = False\n    for event in events:\n        event_type = event.get(\"type\")\n        if stopped:\n            raise ProtocolError(\"event arrived after message_stop\")\n        if event_type == \"content_block_delta\":\n            delta = event.get(\"delta\", {})\n            if delta.get(\"type\") == \"text_delta\":\n                chunks.append(str(delta.get(\"text\", \"\")))\n        elif event_type == \"message_stop\":\n            stopped = True\n    if not stopped:\n        raise ProtocolError(\"stream ended without message_stop\")\n    return \"\".join(chunks)\n\n\ndef batch(items: list[Any], size: int) -> list[list[Any]]:\n    if size < 1:\n        raise ValueError(\"batch size must be positive\")\n    return [items[index : index + size] for index in range(0, len(items), size)]\n\n\ndef stable_cache_key(model: str, stable_prefix: str) -> str:","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/rohitg00/ai-engineering-from-scratch/blob/39ea8a1c6d0b61f071226eff7ede4d4105fed820/certifications/claude/lessons/08-messages-api-and-application-lifecycle/code/main.py#L148-L184","documentation":"Raised by collect_stream_text when a stream event appears after message_stop was already received. message_stop is terminal in the Messages streaming protocol; any subsequent event means the event sequence is malformed or events from two streams were concatenated.","triggerScenarios":"Passing an event list like [{\"type\":\"message_stop\"}, {\"type\":\"content_block_delta\"}] where deltas or any event follow message_stop (test_stream_collector_requires_stop).","commonSituations":"Concatenating chunks from separate streamed responses, reusing a buffer without clearing it between requests, or replaying captured SSE events out of order.","solutions":["Trim the event list so message_stop is last","Clear/rotate stream buffers between requests so events never bleed across messages","If aggregating multiple responses, call collect_stream_text once per response"],"exampleFix":"// before\nevents = resp1_events + resp2_events\ncollect_stream_text(events)\n// after\ntext1 = collect_stream_text(resp1_events)\ntext2 = collect_stream_text(resp2_events)","handlingStrategy":"validation","validationCode":"def stop_is_last(events):\n    stops = [i for i, e in enumerate(events) if e.get(\"type\") == \"message_stop\"]\n    return len(stops) <= 1 and (not stops or stops[0] == len(events) - 1)","typeGuard":"def is_terminated_stream(events: list) -> bool:\n    seen_stop = False\n    for e in events:\n        if seen_stop:\n            return False\n        if e.get(\"type\") == \"message_stop\":\n            seen_stop = True\n    return seen_stop","tryCatchPattern":"try:\n    text = collect_stream_text(events)\nexcept ProtocolError as exc:\n    if \"after message_stop\" in str(exc):\n        split_events_and_reprocess()","preventionTips":["Call collect_stream_text once per streamed response, never on concatenated buffers","Reset the event buffer between requests"],"tags":["streaming","messages-api","event-order","python"],"backgroundTag":"events-after-stream-termination","analyzedSha":"39ea8a1c6d0b61f071226eff7ede4d4105fed820","analyzedAt":"2026-08-26T03:13:46.626Z","schemaVersion":2},"datasetVersion":"2026-08-26T07:17:17.940Z"}