fix(cli): pipe stdin to prompt when no args given (suppress REPL on pipe)

When stdin is not a terminal (pipe or redirect) and no prompt is given on
the command line, claw was starting the interactive REPL and printing the
startup banner, then consuming the pipe without sending anything to the API.

Fix: in parse_args, when rest.is_empty() and stdin is not a terminal, read
stdin synchronously and dispatch as CliAction::Prompt instead of Repl.
Empty pipe still falls through to Repl (interactive launch with no input).

Before: echo 'hello' | claw  -> startup banner + REPL start
After:  echo 'hello' | claw  -> dispatches as one-shot prompt

159 CLI tests pass, fmt clean.
This commit is contained in:
YeonGyu-Kim
2026-04-09 20:36:14 +09:00
parent aef85f8af5
commit 84b77ece4d

View File

@@ -578,6 +578,27 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
if rest.is_empty() {
let permission_mode = permission_mode_override.unwrap_or_else(default_permission_mode);
// When stdin is not a terminal (pipe/redirect) and no prompt is given on the
// command line, read stdin as the prompt and dispatch as a one-shot Prompt
// rather than starting the interactive REPL (which would consume the pipe and
// print the startup banner, then exit without sending anything to the API).
if !std::io::stdin().is_terminal() {
let mut buf = String::new();
let _ = std::io::Read::read_to_string(&mut std::io::stdin(), &mut buf);
let piped = buf.trim().to_string();
if !piped.is_empty() {
return Ok(CliAction::Prompt {
model,
prompt: piped,
allowed_tools,
permission_mode,
output_format,
compact: false,
base_commit,
reasoning_effort,
});
}
}
return Ok(CliAction::Repl {
model,
allowed_tools,