{"record":{"id":"43d8578b4c04e138","repo":"rohitg00/ai-engineering-from-scratch","slug":"response-content-must-be-a-non-empty-block-list","errorCode":null,"errorMessage":"response content must be a non-empty block list","messagePattern":"response content must be a non-empty block list","errorType":"exception","errorClass":"ProtocolError","httpStatus":null,"severity":"error","filePath":"certifications/claude/lessons/08-messages-api-and-application-lifecycle/code/main.py","lineNumber":149,"sourceCode":"            value = handler(arguments)\n            return {\n                \"type\": \"tool_result\",\n                \"tool_use_id\": tool_id,\n                \"content\": json.dumps(value, sort_keys=True),\n            }\n        except Exception as exc:  # Tool failures become model-visible results.\n            return {\n                \"type\": \"tool_result\",\n                \"tool_use_id\": tool_id,\n                \"content\": f\"{type(exc).__name__}: {exc}\",\n                \"is_error\": True,\n            }\n\n\ndef _validated_blocks(response: dict[str, Any]) -> list[dict[str, Any]]:\n    blocks = response.get(\"content\")\n    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\":","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/rohitg00/ai-engineering-from-scratch/blob/39ea8a1c6d0b61f071226eff7ede4d4105fed820/certifications/claude/lessons/08-messages-api-and-application-lifecycle/code/main.py#L131-L167","documentation":"Raised by _validated_blocks when a model response's 'content' field is missing, not a list, or an empty list. The Messages API always returns at least one content block, so an empty or non-list content field indicates a malformed or truncated response.","triggerScenarios":"Calling run() with a response dict like {\"content\": []}, {\"content\": \"text\"}, or one that omits 'content' entirely.","commonSituations":"Stubbing API responses in tests with minimal dicts, partial JSON parsing of a streamed response that was cut off, or upstream schema drift after an API version change.","solutions":["Ensure the response dict has a non-empty 'content' list of block objects","When replaying captures, verify the full response body was saved, not just text","Add a fixture builder that always includes at least one text block"],"exampleFix":"// before\n{\"role\":\"assistant\",\"content\":[]}\n// after\n{\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"done\"}]}","handlingStrategy":"validation","validationCode":"def has_valid_content(response: dict) -> bool:\n    content = response.get(\"content\")\n    return isinstance(content, list) and len(content) > 0","typeGuard":"def is_response_with_content(resp) -> bool:\n    return isinstance(resp, dict) and isinstance(resp.get(\"content\"), list) and bool(resp[\"content\"])","tryCatchPattern":"try:\n    agent.run(...)\nexcept ProtocolError as exc:\n    if \"non-empty block list\" in str(exc):\n        reject_or_refetch_response()","preventionTips":["Never stub a response with empty content; always include one text block","Validate captured or replayed response bodies before use"],"tags":["messages-api","response-validation","python"],"backgroundTag":"empty-response-content","analyzedSha":"39ea8a1c6d0b61f071226eff7ede4d4105fed820","analyzedAt":"2026-08-26T03:13:46.626Z","schemaVersion":2},"datasetVersion":"2026-08-26T07:17:17.940Z"}