Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 67 additions & 8 deletions src/claude_code_sdk/_internal/transport/subprocess_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,70 @@ async def send_request(self, messages: list[Any], options: dict[str, Any]) -> No
"""Not used for CLI transport - args passed via command line."""

async def receive_messages(self) -> AsyncIterator[dict[str, Any]]:
"""Receive messages from CLI."""
"""Receive messages from CLI with improved JSON parsing for chunked responses."""
if not self._process or not self._stdout_stream:
raise CLIConnectionError("Not connected")

stderr_lines = []
json_buffer = ""

def parse_json_chunks(buffer: str) -> tuple[list[dict], str]:
"""Parse complete JSON objects from buffer, return objects and remaining buffer."""
objects = []
remaining = buffer.strip()

while remaining:
# Find potential JSON start
start_idx = -1
for i, char in enumerate(remaining):
if char in '{[':
start_idx = i
break

if start_idx == -1:
break

# Track braces to find complete JSON
brace_count = 0
in_string = False
escape_next = False

for i, char in enumerate(remaining[start_idx:], start_idx):
if escape_next:
escape_next = False
continue

if char == '\\' and in_string:
escape_next = True
continue

if char == '"' and not escape_next:
in_string = not in_string
continue

if not in_string:
if char in '{[':
brace_count += 1
elif char in '}]':
brace_count -= 1

if brace_count == 0:
# Found complete JSON
json_str = remaining[start_idx:i+1]
try:
obj = json.loads(json_str)
objects.append(obj)
remaining = remaining[i+1:].strip()
break
except json.JSONDecodeError:
# Invalid JSON, skip and continue
remaining = remaining[i+1:]
break
else:
# Incomplete JSON, keep in buffer
break

return objects, remaining

async def read_stderr() -> None:
"""Read stderr in background."""
Expand All @@ -188,17 +247,17 @@ async def read_stderr() -> None:
if not line_str:
continue

try:
data = json.loads(line_str)
# Add line to buffer
json_buffer += line_str
Copy link

Copilot AI Jun 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modifying the outer 'json_buffer' inside the nested function may lead to an UnboundLocalError; consider declaring 'nonlocal json_buffer' at the start of the nested function 'read_stderr'.

Copilot uses AI. Check for mistakes.

# Try to parse complete JSON objects
complete_objects, json_buffer = parse_json_chunks(json_buffer)

for data in complete_objects:
try:
yield data
except GeneratorExit:
# Handle generator cleanup gracefully
return
except json.JSONDecodeError as e:
if line_str.startswith("{") or line_str.startswith("["):
raise SDKJSONDecodeError(line_str, e) from e
continue

except anyio.ClosedResourceError:
pass
Expand Down