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

Abierto Apto para principiantes
#1,508 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 2 días

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
2/5
Tiempo estimado
Medio día
Aptitud para principiantes
76/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
python
Área
backend

Línea de trabajo

Empieza en src/claude_agent_sdk/_internal/message_parser.py, en las ramas user, assistant, result y rate_limit_event, donde message, deferred_tool_use y rate_limit_info se indexan sin comprobar la forma. El reproductor del issue ejecuta parse_message con once tramas mal formadas. Ejecútalo y comprueba que cada una lanza MessageParseError con .data establecido, y que tests/test_message_parser.py sigue pasando.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.
Lenguaje dominante
Python
Estrellas
8.5k
Forks
1.4k
Merge medio
3 d 10 h
PR fusionados (30 d)
13

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de anthropics/claude-agent-sdk-python

Todos los issues de anthropics/claude-agent-sdk-python

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.