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
28 changes: 28 additions & 0 deletions src/claude_agent_sdk/_internal/transport/subprocess_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@

_DEFAULT_MAX_BUFFER_SIZE = 1024 * 1024 # 1MB buffer limit
MINIMUM_CLAUDE_CODE_VERSION = "2.0.0"
_LINUX_MAX_ARG_STRLEN_PAGE_MULTIPLIER = 32


def _get_linux_max_arg_strlen() -> int | None:
"""Return Linux's per-argument execve byte limit, if available."""
if platform.system() != "Linux":
return None

try:
page_size = os.sysconf("SC_PAGE_SIZE")
except (AttributeError, OSError, ValueError):
return None

if not isinstance(page_size, int) or page_size <= 0:
return None

return _LINUX_MAX_ARG_STRLEN_PAGE_MULTIPLIER * page_size


# Track live CLI subprocesses so we can terminate them when the parent Python
# process exits. This mirrors the TypeScript SDK's parent-exit cleanup and
Expand Down Expand Up @@ -288,6 +306,16 @@ def _build_command(self) -> list[str]:
if self._options.system_prompt is None:
cmd.extend(["--system-prompt", ""])
elif isinstance(self._options.system_prompt, str):
max_arg_strlen = _get_linux_max_arg_strlen()
system_prompt_size = len(self._options.system_prompt.encode())
if max_arg_strlen is not None and system_prompt_size >= max_arg_strlen:
raise CLIConnectionError(
"system_prompt is too large to pass as a command-line "
f"argument on Linux ({system_prompt_size} bytes; "
f"limit is {max_arg_strlen} bytes). Write the prompt to "
"a file and pass system_prompt={'type': 'file', "
"'path': '/path/to/prompt.txt'} instead."
)
cmd.extend(["--system-prompt", self._options.system_prompt])
else:
sp = self._options.system_prompt
Expand Down
5 changes: 4 additions & 1 deletion src/claude_agent_sdk/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1752,7 +1752,10 @@ class ClaudeAgentOptions:
system_prompt: str | SystemPromptPreset | SystemPromptFile | None = None
"""System prompt configuration.

- ``str`` — Use a custom system prompt.
- ``str`` — Use a custom system prompt. For very large prompts on Linux,
prefer the file form to avoid the kernel's per-argument size limit.
- ``{"type": "file", "path": "/path/to/prompt.txt"}`` — Read a custom
system prompt from a file.
- ``{"type": "preset", "preset": "claude_code"}`` — Use Claude Code's default
system prompt.
- ``{"type": "preset", "preset": "claude_code", "append": "..."}`` — Default
Expand Down
43 changes: 43 additions & 0 deletions tests/test_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,49 @@ def test_build_command_with_system_prompt_string(self):
assert "--system-prompt" in cmd
assert "Be helpful" in cmd

def test_build_command_rejects_oversized_system_prompt_on_linux(self):
"""Test oversized string prompts get an actionable error on Linux."""
from claude_agent_sdk._errors import CLIConnectionError

transport = SubprocessCLITransport(
prompt="test",
options=make_options(system_prompt="x" * 64),
)

with (
patch(
"claude_agent_sdk._internal.transport.subprocess_cli.platform.system",
return_value="Linux",
),
patch(
"claude_agent_sdk._internal.transport.subprocess_cli.os.sysconf",
return_value=1,
),
pytest.raises(CLIConnectionError) as exc_info,
):
transport._build_command()

message = str(exc_info.value)
assert "system_prompt is too large" in message
assert "64 bytes" in message
assert "system_prompt={'type': 'file'" in message

def test_build_command_allows_large_system_prompt_off_linux(self):
"""Test non-Linux platforms keep passing string prompts directly."""
transport = SubprocessCLITransport(
prompt="test",
options=make_options(system_prompt="x" * 16),
)

with patch(
"claude_agent_sdk._internal.transport.subprocess_cli.platform.system",
return_value="Windows",
):
cmd = transport._build_command()

assert "--system-prompt" in cmd
assert cmd[cmd.index("--system-prompt") + 1] == "x" * 16

def test_build_command_with_system_prompt_preset(self):
"""Test building CLI command with system prompt preset."""
transport = SubprocessCLITransport(
Expand Down