`parse_message` raises raw `TypeError` instead of `MessageParseError` on non-dict nested payloads

Đang mở Phù hợp với người mới
#1,508 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 2 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
2/5
Thời gian dự kiến
Nửa ngày
Mức phù hợp với người mới
76/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
python
Lĩnh vực
backend

Hướng nghiên cứu

Bắt đầu từ src/claude_agent_sdk/_internal/message_parser.py, tại các nhánh user, assistant, result và rate_limit_event, nơi message, deferred_tool_use và rate_limit_info được truy cập bằng chỉ số mà không kiểm tra cấu trúc. Đoạn tái hiện trong issue chạy parse_message trên mười một khung dữ liệu sai định dạng. Hãy chạy nó và kiểm tra xem mỗi khung có ném MessageParseError với .data được đặt hay không, và tests/test_message_parser.py vẫn pass.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

bug good first issue

Summary

parse_message raises a raw TypeError instead of the documented MessageParseError when a frame's nested payload (message, deferred_tool_use, rate_limit_info) is present but not a dict, because each branch catches only KeyError. Callers catching ClaudeSDKError / MessageParseError do not catch it, so the message stream dies with an undocumented exception type and no .data payload.

This is a correctness/error-contract bug, not a security vulnerability: the frames come from the trusted local CLI subprocess.

Related: #1058 fixed the sibling case (string/non-dict content inside dict messages) in 0.2.111; this issue covers the distinct nested-payload layer (message / deferred_tool_use / rate_limit_info themselves non-dict), which still raises raw TypeError at the pinned commit.

Version

  • Repo: anthropics/claude-agent-sdk-python
  • Package: claude-agent-sdk 0.2.181
  • Commit: 22b8eb9c19adfa78759b79e22bd664a57843bb06 (main, 2026-11-12)

Location

src/claude_agent_sdk/_internal/message_parser.py. The docstring promises MessageParseError when parsing fails (:60-61), yet each branch catches only KeyError (user :146, assistant :221, result :338, rate_limit_event :373), while non-dict nested values fail at subscripting before any KeyError can arise:

  • user :102 — data["message"]["content"] with message a string/list/None
  • assistant :153 — same subscript
  • result :325-329 — deferred["id"] with deferred_tool_use a string/list/bool (the guard is truthiness-only)
  • rate_limit :358-361 — info["status"] with rate_limit_info a string/list/None

Both call sites are unguarded (src/claude_agent_sdk/client.py:267-270; src/claude_agent_sdk/_internal/client.py:204-207), and Query.receive_messages yields raw dicts with no shape validation (src/claude_agent_sdk/_internal/query.py:1164-1176). MessageParseError subclasses ClaudeSDKError and carries .data (src/claude_agent_sdk/_errors.py:129-134). The no-raw-TypeError contract is pinned by tests asserting sibling malformed shapes raise MessageParseError (tests/test_message_parser.py:1002-1016).

All traced failures are TypeError only — no AttributeError path was observed from JSON-decoded input.

Minimal repro

from claude_agent_sdk._internal.message_parser import parse_message
from claude_agent_sdk import ClaudeSDKError

frames = [
    {"type": "user", "message": "oops"},
    {"type": "user", "message": []},
    {"type": "user", "message": None},
    {"type": "assistant", "message": []},
    {"type": "assistant", "message": "oops"},
    {"type": "result", "subtype": "success", "duration_ms": 1,
     "duration_api_ms": 1, "is_error": False, "num_turns": 1,
     "session_id": "sess-1", "deferred_tool_use": "x"},
    {"type": "result", "subtype": "success", "duration_ms": 1,
     "duration_api_ms": 1, "is_error": False, "num_turns": 1,
     "session_id": "sess-1", "deferred_tool_use": [1]},
    {"type": "result", "subtype": "success", "duration_ms": 1,
     "duration_api_ms": 1, "is_error": False, "num_turns": 1,
     "session_id": "sess-1", "deferred_tool_use": True},
    {"type": "rate_limit_event", "rate_limit_info": "x"},
    {"type": "rate_limit_event", "rate_limit_info": []},
    {"type": "rate_limit_event", "rate_limit_info": None},
]

for frame in frames:
    try:
        parse_message(frame)
    except ClaudeSDKError as exc:
        print("MessageParseError:", exc)
    except TypeError as exc:
        print("RAW TypeError:", exc)

Observed: all 11 shapes raise raw TypeError (e.g. string indices must be integers, not 'str', 'NoneType' object is not subscriptable), which escapes an except ClaudeSDKError handler.

Expected: all 11 shapes raise MessageParseError with the raw data attached as .data, like the sibling malformed shapes (missing key, non-list assistant content, top-level non-dict) already do.

Impact

Applications that handle malformed CLI output by catching ClaudeSDKError / MessageParseError — the documented contract — miss these failures entirely: the stream dies with a raw TypeError and no .data payload for diagnostics. The exception-type inconsistency also makes the parser's error behavior unpredictable across malformed-frame shapes. Local error-handling impact only.

Suggested fix

Catch TypeError alongside KeyError in each parse_message branch (or validate that message / deferred_tool_use / rate_limit_info are dicts before subscripting) and raise MessageParseError with the raw data attached.

Environment

  • Python 3.12.3, Linux
  • Verified against the pinned commit above by driving parse_message directly with in-memory dicts (the exact values json.loads would produce from CLI stdout lines). Valid frames parse; valid-error frames already raise MessageParseError with .data. The CLI was not spawned and the Query stream was not run; the unguarded call sites are established by code trace.
Ngôn ngữ chính
Python
Star
8.5k
Fork
1.4k
Merge trung bình
3 ngày 10 giờ
Pull request đã merge (30 ngày)
13

Chuẩn bị môi trường

Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của anthropics/claude-agent-sdk-python

Tất cả issue của anthropics/claude-agent-sdk-python

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.