From d77c8d9364adc1e95b973cfcfc9faf7a3ca0a291 Mon Sep 17 00:00:00 2001 From: w3lld1 <42353747+w3lld1@users.noreply.github.com> Date: Fri, 10 Jul 2026 02:36:25 +0000 Subject: [PATCH] fix: explain oversized system prompts --- .../_internal/transport/subprocess_cli.py | 28 ++++++++++++ src/claude_agent_sdk/types.py | 5 ++- tests/test_transport.py | 43 +++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/claude_agent_sdk/_internal/transport/subprocess_cli.py b/src/claude_agent_sdk/_internal/transport/subprocess_cli.py index e9a522d13..db73e2935 100644 --- a/src/claude_agent_sdk/_internal/transport/subprocess_cli.py +++ b/src/claude_agent_sdk/_internal/transport/subprocess_cli.py @@ -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 @@ -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 diff --git a/src/claude_agent_sdk/types.py b/src/claude_agent_sdk/types.py index 5b7926449..d634d554b 100644 --- a/src/claude_agent_sdk/types.py +++ b/src/claude_agent_sdk/types.py @@ -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 diff --git a/tests/test_transport.py b/tests/test_transport.py index d55aa18b0..c1c23f069 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -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(