From ab839bcfdaa48c13a5067186dc2ec0f72208eed6 Mon Sep 17 00:00:00 2001 From: clawdbot Date: Tue, 13 Oct 2026 00:09:51 +0000 Subject: [PATCH] fix: restore is_terminal() check for spinner in run_turn The terminal check was removed during merge conflict resolution, causing the spinner animation to be output to non-TTY streams (like pipes used in tests), which corrupted the output format and broke the text_prompt_mode_keeps_streamed_text_when_the_turn_fails test. Restore the is_terminal() check to only animate when stdout is a real terminal while keeping the interrupt feature. --- rust/crates/rusty-claude-cli/src/main.rs | 26 ++++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 45c055d2..2d3af141 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -7895,15 +7895,23 @@ impl LiveCli { } let mut spinner = Spinner::new(); let mut stdout = io::stdout(); - spinner.tick( - if escape_monitor.is_some() { - "🦀 Thinking... (esc to interrupt)" - } else { - "🦀 Thinking..." - }, - TerminalRenderer::new().color_theme(), - &mut stdout, - )?; + // The animated frame is cursor control: it saves the cursor, rewrites the + // current line, and restores the cursor. None of that describes anything + // when stdout is a pipe — the escapes survive as literal bytes and the + // banner ends up glued to whatever the emitter writes next. Only animate + // on a terminal; the one-shot completion line below is plain text and is + // emitted either way. + if io::stdout().is_terminal() { + spinner.tick( + if escape_monitor.is_some() { + "🦀 Thinking... (esc to interrupt)" + } else { + "🦀 Thinking..." + }, + TerminalRenderer::new().color_theme(), + &mut stdout, + )? + } let result = runtime.run_turn(input, Some(&mut permission_prompter)); if let Some(monitor) = escape_monitor { monitor.stop();