You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Bug: _chunk_bytes_to_sse raises AttributeError when chunk.get('bytes') is None in Bedrock stream decoder #1975
When AWS Bedrock sends an empty chunk frame, a control payload, or a chunk dictionary where the "bytes" key is missing or None (e.g. chunk = {"bytes": None} or chunk = {}), chunk.get("bytes") evaluates to None.
Passing None to _chunk_bytes_to_sse raises AttributeError: 'NoneType' object has no attribute 'decode', immediately crashing the active Bedrock stream.
To Reproduce
fromanthropic.lib.bedrock._stream_decoderimport_chunk_bytes_to_sse_chunk_bytes_to_sse(None)
# Traceback (most recent call last):# File ".../anthropic/lib/bedrock/_stream_decoder.py", line 72, in _chunk_bytes_to_sse# decoded = raw.decode()# AttributeError: 'NoneType' object has no attribute 'decode'
Expected Behavior
_chunk_bytes_to_sse should safely guard against None or non-bytes payloads and return None (dropping the empty frame) rather than crashing with an AttributeError.
Thanks for the clear write-up with a one-line repro, and for opening #1976 so quickly.
As far as I can tell, None can't reach the helper through a real stream on main (bcd55fe, v1.15.0). botocore's JSON parser skips null and missing members, so {}, {"bytes": null} and an empty body all parse to {'chunk': {}}, and if not chunk: return None (_stream_decoder.py L65) drops them first. botocore 1.31.57 (the pyproject.toml floor) parses them the same way.
# main @ bcd55fe
{} / {'bytes': null} / empty body / unknown type ['message_start', 'message_stop']
{'bytes': ''} ['message_start'] then JSONDecodeError
# #1976 @ bfe041a
{} / {'bytes': null} / empty body / unknown type ['message_start', 'message_stop']
{'bytes': ''} ['message_start'] then JSONDecodeError
# main + call-site falsy check (below)
{} / {'bytes': null} / empty body / unknown type ['message_start', 'message_stop']
{'bytes': ''} ['message_start', 'message_stop']
The one input I found that gets past that line is {"bytes": ""}, and the isinstance check in #1976 lets b"" through. If you'd like to keep a guard, a falsy check at the call site mirrors the line above and needs no pyright: ignore:
Thanks @kimnamu for the thorough investigation and the clean call-site approach! I've updated #1976 accordingly with the falsy check (if not raw: return None), removing the pyright ignore and adding the regression tests for empty and null byte frames. All tests pass locally clean.
Activity
kimnamu commentedon Oct 16, 2026
Thanks for the clear write-up with a one-line repro, and for opening #1976 so quickly.
As far as I can tell,
Nonecan't reach the helper through a real stream onmain(bcd55fe, v1.15.0). botocore's JSON parser skipsnulland missing members, so{},{"bytes": null}and an empty body all parse to{'chunk': {}}, andif not chunk: return None(_stream_decoder.pyL65) drops them first. botocore 1.31.57 (thepyproject.tomlfloor) parses them the same way.The one input I found that gets past that line is
{"bytes": ""}, and theisinstancecheck in #1976 letsb""through. If you'd like to keep a guard, a falsy check at the call site mirrors the line above and needs nopyright: ignore:With it,
uv run pyright src/anthropic/lib/bedrockis clean and the four Bedrock/AWS test files pass (205). Happy to leave it with you.Repro script
chauvuusvn commentedon Oct 16, 2026
Thanks for the great feedback and the thorough analysis, @kimnamu!
I've updated PR #1976 following your suggestion:
raw = chunk.get("bytes")at the call site withif not raw: return None, cleanly dropping bothNoneand emptyb""._chunk_bytes_to_sse(raw: bytes)without anypyright: ignorecomments.test_stream_skips_empty_and_null_bytes_chunk) simulating frames with both empty and null bytes.All Bedrock tests and type checks are passing cleanly. Appreciate the guidance!
chauvuusvn commentedon Oct 21, 2026
Thanks @kimnamu for the thorough investigation and the clean call-site approach! I've updated #1976 accordingly with the falsy check (
if not raw: return None), removing the pyright ignore and adding the regression tests for empty and null byte frames. All tests pass locally clean.