Skip to content

system_prompt over 131,071 bytes dies with 'Argument list too long' before any API request #1096

Description

@qing-ant

Symptom

A system_prompt string of 131,072 bytes or more kills the CLI subprocess before any API request is made:

claude_agent_sdk._errors.CLIConnectionError: Failed to start Claude Code: [Errno 7] Argument list too long: '<cli path>'

131,071 bytes spawns fine. 131,072 fails. Reproduces on every CLI version tested.

Mechanism

src/claude_agent_sdk/_internal/transport/subprocess_cli.py:291 passes the prompt as a single argv element:

elif isinstance(self._options.system_prompt, str):
    cmd.extend(["--system-prompt", self._options.system_prompt])

Linux caps a single argv element at MAX_ARG_STRLEN = 32 × PAGE_SIZE = 131,072 bytes. This is a per-element limit, distinct from ARG_MAX, and is not raisable via ulimit.

Reproduced directly, independent of the SDK:

import subprocess, os
os.sysconf('SC_PAGE_SIZE')   # 4096
subprocess.run(['/bin/true', 'x'*131071])   # OK
subprocess.run(['/bin/true', 'x'*131072])   # OSError errno 7, Argument list too long

The failure is in execve, so it precedes anything the CLI or the API would do with the prompt.

There is a working escape hatch

system_prompt={"type": "file", "path": "/path/to/prompt.txt"}

which routes to --system-prompt-file (subprocess_cli.py:295). Verified with a 200 KB prompt and a ~1 MiB prompt.

This shipped for #267 ("Allow passing system prompts as files", closed). That issue reported the same Argument list too long traceback; the feature landed and closed it.

So what's left

The escape hatch exists but nothing points you at it. A caller who passes a long str still gets a raw OSError wrapped in CLIConnectionError, naming the CLI path and nothing else — no mention of system_prompt, of a size limit, or of the file form that would fix it. Diagnosing it requires knowing about MAX_ARG_STRLEN.

This is arguably a docs fix plus a clearer error, not a code fix. Options, cheapest first:

  1. Actionable error. In _build_command, check len(system_prompt.encode()) against the threshold and raise something that names system_prompt, the measured size, the limit, and {"type": "file", "path": ...}.
  2. Auto-spill. Above the threshold, write the prompt to a temp file and pass --system-prompt-file transparently. Silently correct, but adds temp-file lifetime management.
  3. Docs. Note the limit and the file form wherever system_prompt is documented.

(1) + (3) seems right. (2) is defensible but hides a real platform constraint.

The exact threshold is platform-dependent (32 * os.sysconf('SC_PAGE_SIZE')); hardcoding 131072 is wrong on a non-4 KiB-page system, and the limit does not exist at all on Windows.

Separately: the SDK inherits os.environ minus one var

subprocess_cli.py:491:

inherited_env = {k: v for k, v in os.environ.items() if k != "CLAUDECODE"}

Only CLAUDECODE is filtered. So an SDK process launched from inside a coordinator-mode CLI silently inherits CLAUDE_CODE_COORDINATOR_MODE=1 (a real CLI env var — it gates isCoordinatorMode()), which shrinks the injected agent/skill/MCP block and changes caching behavior.

This is surprising: the SDK is a separate agent, and nothing about the parent's coordinator mode should follow it. Worth either documenting or adding to the filter alongside CLAUDECODE (see #573, which is why CLAUDECODE is filtered in the first place).

Severity

Low-to-moderate. There is a working workaround, and it is already the documented-by-existence API. The cost is diagnosis time: the error names neither the cause nor the cure.

Not verified

  • The 131,071/131,072 boundary I re-measured myself, on Linux, PAGE_SIZE 4096, against /bin/true. The CLIConnectionError traceback and the 200 KB / 1 MiB --system-prompt-file successes are from the originating investigation; I did not re-run them.
  • The "~19×" shrink figure originally reported for the coordinator-mode block is not re-verified and is omitted above. What I did confirm: the SDK inherits everything but CLAUDECODE (subprocess_cli.py:491), and CLAUDE_CODE_COORDINATOR_MODE is read by the CLI (src/coordinator/coordinatorMode.ts:48 in claude-cli-internal).
  • Not tested on macOS or Windows.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdocumentationImprovements or additions to documentationenhancementNew feature or requestgood first issueGood for newcomers

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions