diff --git a/src/claude_agent_sdk/_errors.py b/src/claude_agent_sdk/_errors.py index 5f3eb9c70..dbd69779b 100644 --- a/src/claude_agent_sdk/_errors.py +++ b/src/claude_agent_sdk/_errors.py @@ -120,10 +120,16 @@ def __init__( class CLIJSONDecodeError(ClaudeSDKError): """Raised when unable to decode JSON from CLI output.""" - def __init__(self, line: str, original_error: Exception): + def __init__(self, line: str, original_error: Exception, hint: str | None = None): self.line = line self.original_error = original_error - super().__init__(f"Failed to decode JSON: {line[:100]}...") + self.hint = hint + message = f"Failed to decode JSON: {line[:100]}..." + # Appended AFTER the truncated line so actionable guidance survives: + # `line` is cut at 100 chars, so anything folded into it can be lost. + if hint: + message = f"{message} {hint}" + super().__init__(message) class MessageParseError(ClaudeSDKError): diff --git a/src/claude_agent_sdk/_internal/transport/subprocess_cli.py b/src/claude_agent_sdk/_internal/transport/subprocess_cli.py index 58abc438d..fe0611f0f 100644 --- a/src/claude_agent_sdk/_internal/transport/subprocess_cli.py +++ b/src/claude_agent_sdk/_internal/transport/subprocess_cli.py @@ -1095,6 +1095,11 @@ def guard(length: int) -> None: ValueError( f"Buffer size {length} exceeds limit {self._max_buffer_size}" ), + hint=( + "Large tool results (file reads, images, MCP responses) " + "can exceed the default; raise it with " + "ClaudeAgentOptions(max_buffer_size=...)." + ), ) try: diff --git a/tests/test_errors.py b/tests/test_errors.py index 693def109..753b6ee9c 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -110,3 +110,13 @@ def test_json_decode_error(self): assert error.line == "{invalid json}" assert error.original_error == e assert "Failed to decode JSON" in str(error) + assert error.hint is None + + def test_json_decode_error_hint_survives_line_truncation(self): + """The hint must outlive the 100-char truncation of `line`.""" + error = CLIJSONDecodeError("x" * 500, ValueError("boom"), hint="Do the thing.") + assert error.hint == "Do the thing." + assert str(error).endswith("Do the thing.") + # The line is still truncated; the hint is what stays readable. + assert "x" * 100 in str(error) + assert "x" * 101 not in str(error) diff --git a/tests/test_subprocess_buffering.py b/tests/test_subprocess_buffering.py index daf79dc53..3091053af 100644 --- a/tests/test_subprocess_buffering.py +++ b/tests/test_subprocess_buffering.py @@ -281,6 +281,9 @@ async def _test() -> None: pass assert f"maximum buffer size of {custom_limit} bytes" in str(exc_info.value) + # The error must name the option that fixes it, so users reach for + # max_buffer_size instead of editing the installed package. + assert "max_buffer_size" in str(exc_info.value) anyio.run(_test)