Skip to content

Commit eeff81f

Browse files
authored
transport: drop --debug-to-stderr detection (prep for CLI flag removal) (#860)
The CLI's `--debug-to-stderr` flag is being removed in favor of `--debug-file` (TS SDK already migrated in anthropics/claude-cli-internal#30630). **Unlike the TS SDK, this SDK never set `--debug-to-stderr` internally** — it only *detected* whether users passed it via `extra_args`, to decide whether to pipe stderr and feed the deprecated `debug_stderr` file object. So there is no internal flag-send to switch to `--debug-file`. This PR: - Pipes stderr based solely on whether a `stderr` callback is registered (drops the `"debug-to-stderr" in extra_args` checks). - Removes the deprecated `debug_stderr` write path (already deprecated; now unreachable). The field stays on `ClaudeAgentOptions` for source compat but is no longer read by the transport. - Updates `examples/stderr_callback_example.py` to not pass `debug-to-stderr`; points users at `extra_args={"debug-file": "/path"}` for verbose CLI logs. - Drops the e2e test that asserted on `[DEBUG]` stderr content (depended on the flag); the no-debug callback test remains. **Behavior delta:** users who currently pass `extra_args={"debug-to-stderr": None}` with only `debug_stderr=` (no `stderr=` callback) will no longer get debug output written to that file object. Migration: use the `stderr` callback, or pass `extra_args={"debug-file": "/path"}` and read the file. Unblocks step 2 (removing `--debug-to-stderr` from the CLI) without breaking this SDK's example/e2e on the next CLI release.
1 parent d570b8a commit eeff81f

4 files changed

Lines changed: 9 additions & 50 deletions

File tree

e2e-tests/test_stderr_callback.py

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,10 @@
55
from claude_agent_sdk import ClaudeAgentOptions, query
66

77

8-
@pytest.mark.e2e
9-
@pytest.mark.asyncio
10-
async def test_stderr_callback_captures_debug_output():
11-
"""Test that stderr callback receives debug output when enabled."""
12-
stderr_lines = []
13-
14-
def capture_stderr(line: str):
15-
stderr_lines.append(line)
16-
17-
# Enable debug mode to generate stderr output
18-
options = ClaudeAgentOptions(
19-
stderr=capture_stderr, extra_args={"debug-to-stderr": None}
20-
)
21-
22-
# Run a simple query
23-
async for _ in query(prompt="What is 1+1?", options=options):
24-
pass # Just consume messages
25-
26-
# Verify we captured debug output
27-
assert len(stderr_lines) > 0, "Should capture stderr output with debug enabled"
28-
assert any("[DEBUG]" in line for line in stderr_lines), (
29-
"Should contain DEBUG messages"
30-
)
31-
32-
338
@pytest.mark.e2e
349
@pytest.mark.asyncio
3510
async def test_stderr_callback_without_debug():
36-
"""Test that stderr callback works but receives no output without debug mode."""
11+
"""Test that stderr callback is wired up and receives no output on a clean run."""
3712
stderr_lines = []
3813

3914
def capture_stderr(line: str):

examples/stderr_callback_example.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@ def stderr_callback(message: str):
1818
if "[ERROR]" in message:
1919
print(f"Error detected: {message}")
2020

21-
# Create options with stderr callback and enable debug mode
22-
options = ClaudeAgentOptions(
23-
stderr=stderr_callback,
24-
extra_args={"debug-to-stderr": None} # Enable debug output
25-
)
21+
# Create options with stderr callback. The callback receives any stderr the
22+
# CLI emits (warnings, errors). For verbose CLI debug logs, pass
23+
# extra_args={"debug-file": "/path/to/log"} and read that file instead.
24+
options = ClaudeAgentOptions(stderr=stderr_callback)
2625

2726
# Run a query
2827
print("Running query with stderr capture...")

src/claude_agent_sdk/_internal/transport/subprocess_cli.py

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -439,14 +439,8 @@ async def connect(self) -> None:
439439
if self._cwd:
440440
process_env["PWD"] = self._cwd
441441

442-
# Pipe stderr if we have a callback OR debug mode is enabled
443-
should_pipe_stderr = (
444-
self._options.stderr is not None
445-
or "debug-to-stderr" in self._options.extra_args
446-
)
447-
448-
# For backward compat: use debug_stderr file object if no callback and debug is on
449-
stderr_dest = PIPE if should_pipe_stderr else None
442+
# Pipe stderr only when the caller registered a callback.
443+
stderr_dest = PIPE if self._options.stderr is not None else None
450444

451445
self._process = await anyio.open_process(
452446
cmd,
@@ -462,7 +456,7 @@ async def connect(self) -> None:
462456
self._stdout_stream = TextReceiveStream(self._process.stdout)
463457

464458
# Setup stderr stream if piped
465-
if should_pipe_stderr and self._process.stderr:
459+
if stderr_dest is PIPE and self._process.stderr:
466460
self._stderr_stream = TextReceiveStream(self._process.stderr)
467461
# Start async task to read stderr
468462
self._stderr_task_group = anyio.create_task_group()
@@ -505,15 +499,6 @@ async def _handle_stderr(self) -> None:
505499
# Call the stderr callback if provided
506500
if self._options.stderr:
507501
self._options.stderr(line_str)
508-
509-
# For backward compatibility: write to debug_stderr if in debug mode
510-
elif (
511-
"debug-to-stderr" in self._options.extra_args
512-
and self._options.debug_stderr
513-
):
514-
self._options.debug_stderr.write(line_str + "\n")
515-
if hasattr(self._options.debug_stderr, "flush"):
516-
self._options.debug_stderr.flush()
517502
except anyio.ClosedResourceError:
518503
pass # Stream closed, exit normally
519504
except Exception:

src/claude_agent_sdk/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1467,7 +1467,7 @@ class ClaudeAgentOptions:
14671467
max_buffer_size: int | None = None # Max bytes when buffering CLI stdout
14681468
debug_stderr: Any = (
14691469
sys.stderr
1470-
) # Deprecated: File-like object for debug output. Use stderr callback instead.
1470+
) # Deprecated and no longer read by the transport. Use the stderr callback.
14711471
stderr: Callable[[str], None] | None = None # Callback for stderr output from CLI
14721472

14731473
# Tool permission callback

0 commit comments

Comments
 (0)