From f27a5e187a38613e37fa02769dda2f302a6440ed Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Tue, 10 Mar 2026 11:58:07 -0400 Subject: [PATCH] Fix verbose mode breaking terminal rendering after many tool calls In verbose mode, completed tool call bars accumulated in indicatif's MultiProgress without limit. After ~80+ bars exceeded the terminal height, indicatif's cursor repositioning broke and all rendering stopped. Now in verbose mode, completed tool calls are printed as permanent output via multi.println() and their bars are removed from MultiProgress. Only running tool calls remain as live bars with spinner + elapsed time. This keeps MultiProgress small regardless of how many tool calls execute. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/crates/arc-workflows/src/cli/progress.rs | 47 +++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/lib/crates/arc-workflows/src/cli/progress.rs b/lib/crates/arc-workflows/src/cli/progress.rs index 7ae80940f..47a41d62e 100644 --- a/lib/crates/arc-workflows/src/cli/progress.rs +++ b/lib/crates/arc-workflows/src/cli/progress.rs @@ -770,8 +770,8 @@ impl ProgressUI { bar.finish_and_clear(); } for entry in &stage.tool_calls { - if entry.is_branch || self.verbose { - // Keep visible: branches always, all entries in verbose mode + if entry.is_branch { + // Keep visible: branches always stay entry.bar.abandon(); } else { entry.bar.finish_and_clear(); @@ -1093,25 +1093,40 @@ impl ProgressUI { } fn on_tool_call_completed(&mut self, stage_node_id: &str, tool_call_id: &str, is_error: bool) { - if let ProgressRenderer::Tty(_) = &self.renderer { + if let ProgressRenderer::Tty(tty) = &self.renderer { if let Some(stage) = self.active_stages.get_mut(stage_node_id) { - if let Some(entry) = stage + if let Some(pos) = stage .tool_calls - .iter_mut() - .find(|e| e.tool_call_id == tool_call_id) + .iter() + .position(|e| e.tool_call_id == tool_call_id) { let glyph = if is_error { red_cross() } else { green_check() }; - entry.status = if is_error { - ToolCallStatus::Failed + let elapsed = format_duration_short(stage.tool_calls[pos].bar.elapsed()); + + if self.verbose { + // Print as permanent output above the MultiProgress region, + // then remove the bar so MultiProgress stays small. + let dim = Style::new().dim(); + let _ = tty.multi.println(format!( + " {glyph} {} {}", + stage.tool_calls[pos].display_name, + dim.apply_to(&elapsed) + )); + stage.tool_calls[pos].bar.finish_and_clear(); + stage.tool_calls.remove(pos); } else { - ToolCallStatus::Succeeded - }; - let elapsed = format_duration_short(entry.bar.elapsed()); - entry.bar.set_style(style_tool_done()); - entry.bar.set_prefix(elapsed); - entry - .bar - .finish_with_message(format!("{glyph} {}", entry.display_name)); + let entry = &mut stage.tool_calls[pos]; + entry.status = if is_error { + ToolCallStatus::Failed + } else { + ToolCallStatus::Succeeded + }; + entry.bar.set_style(style_tool_done()); + entry.bar.set_prefix(elapsed); + entry + .bar + .finish_with_message(format!("{glyph} {}", entry.display_name)); + } } } }