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) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-10 11:58:07 -04:00
parent ec7fc8d5a2
commit f27a5e187a

View file

@ -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));
}
}
}
}