Skip to content
Open
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions src/claude_agent_sdk/_internal/message_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ def parse_message(data: dict[str, Any]) -> Message:
usage=data.get("usage"),
result=data.get("result"),
structured_output=data.get("structured_output"),
errors=data.get("errors"),
)
except KeyError as e:
raise MessageParseError(
Expand Down
24 changes: 18 additions & 6 deletions src/claude_agent_sdk/_internal/transport/subprocess_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def __init__(
if options.max_buffer_size is not None
else _DEFAULT_MAX_BUFFER_SIZE
)
self._stderr_lines: list[str] = []
self._temp_files: list[str] = [] # Track temporary files for cleanup
self._write_lock: anyio.Lock = anyio.Lock()

Expand Down Expand Up @@ -391,11 +392,10 @@ async def connect(self) -> None:
if self._cwd:
process_env["PWD"] = self._cwd

# Pipe stderr if we have a callback OR debug mode is enabled
should_pipe_stderr = (
self._options.stderr is not None
or "debug-to-stderr" in self._options.extra_args
)
# Always pipe stderr so we can capture it for error reporting.
# The callback and debug mode flags control whether lines are
# forwarded in real-time, but we always collect them.
should_pipe_stderr = True

# For backward compat: use debug_stderr file object if no callback and debug is on
stderr_dest = PIPE if should_pipe_stderr else None
Expand Down Expand Up @@ -457,6 +457,9 @@ async def _handle_stderr(self) -> None:
if not line_str:
continue

# Always collect stderr lines for error reporting
self._stderr_lines.append(line_str)

# Call the stderr callback if provided
if self._options.stderr:
self._options.stderr(line_str)
Expand Down Expand Up @@ -618,12 +621,21 @@ async def _read_messages_impl(self) -> AsyncIterator[dict[str, Any]]:
except Exception:
returncode = -1

# Wait for stderr reader to finish draining
if self._stderr_task_group:
with suppress(Exception):
with anyio.move_on_after(5):
self._stderr_task_group.cancel_scope.cancel()
await self._stderr_task_group.__aexit__(None, None, None)
self._stderr_task_group = None

# Use exit code for error detection
if returncode is not None and returncode != 0:
stderr_output = "\n".join(self._stderr_lines) if self._stderr_lines else None
self._exit_error = ProcessError(
f"Command failed with exit code {returncode}",
exit_code=returncode,
stderr="Check stderr output for details",
stderr=stderr_output,
)
raise self._exit_error

Expand Down
1 change: 1 addition & 0 deletions src/claude_agent_sdk/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ class ResultMessage:
usage: dict[str, Any] | None = None
result: str | None = None
structured_output: Any = None
errors: list[str] | None = None


@dataclass
Expand Down