diff --git a/pipeline/haiku.py b/pipeline/haiku.py index b0793d7..1f41c5c 100644 --- a/pipeline/haiku.py +++ b/pipeline/haiku.py @@ -97,9 +97,13 @@ def call_haiku( RuntimeError: If the subprocess times out or exits with a non-zero return code, or if the JSON response cannot be parsed. """ + # Prompt goes on STDIN, not argv: a session extract can exceed Linux's + # MAX_ARG_STRLEN (128KB per single argument), which raises E2BIG ("Argument + # list too long") at exec time and silently kills saves of long sessions. + # `claude -p` with no positional prompt reads the prompt from stdin. cmd = [ "claude", - "-p", prompt, + "-p", "--output-format", "json", "--no-session-persistence", "--exclude-dynamic-system-prompt-sections", @@ -116,6 +120,7 @@ def call_haiku( try: result = subprocess.run( cmd, + input=prompt, capture_output=True, text=True, # claude emits UTF-8; without this, text=True decodes with the diff --git a/tests/test_haiku.py b/tests/test_haiku.py index 76134da..d41f856 100644 --- a/tests/test_haiku.py +++ b/tests/test_haiku.py @@ -140,6 +140,26 @@ def test_call_haiku_success(mock_run): assert "CLAUDECODE" not in env +@patch("pipeline.haiku.subprocess.run") +def test_call_haiku_sends_prompt_on_stdin_not_argv(mock_run): + """The prompt is delivered on STDIN, never as an argv string. + + A session extract can exceed Linux's MAX_ARG_STRLEN (131072 bytes / 128KB + per single argument); the old ``claude -p `` form fails at exec() + with OSError E2BIG ("Argument list too long"), silently losing the save. + Guard both halves so the regression can't silently return: the prompt must + arrive via ``input=`` and must NOT appear in the command argv (``-p`` is a + bare flag, immediately followed by the next option).""" + mock_run.return_value = MagicMock( + returncode=0, stdout=_mock_claude_response("ok"), stderr="") + call_haiku("the full prompt text") + args = mock_run.call_args + cmd = args[0][0] + assert args[1]["input"] == "the full prompt text" + assert "the full prompt text" not in cmd + assert cmd[cmd.index("-p") + 1] == "--output-format" + + @patch("pipeline.haiku.subprocess.run") def test_call_haiku_strips_parent_session_env(mock_run, monkeypatch): """The nested claude -p must not inherit the PARENT Claude Code session