-
Notifications
You must be signed in to change notification settings - Fork 2
bug(tui): tool calls duplicated in TUI mode — ToolStart and ToolOutput sent twice per tool execution #2116
Description
Summary
In TUI mode every tool call is displayed twice in the chat. Each tool produces two ToolStart entries (two $ command lines) and two ToolOutput entries (two result blocks).
Root Cause
There are two independent code paths that both deliver AgentEvent::ToolStart and AgentEvent::ToolOutput into the same agent_event_tx channel that the TUI reads from:
Path 1 — Channel trait (TuiChannel in crates/zeph-tui/src/channel.rs):
TuiChannel::send_tool_start→AgentEvent::ToolStartTuiChannel::send_tool_output→AgentEvent::ToolOutput
These are called by the agent loop in crates/zeph-core/src/agent/tool_execution/native.rs (lines 833, 1421, 1492, 1546) and legacy.rs (lines 605, 633, 691, 709).
Path 2 — Tool event bridge (src/tui_bridge.rs::forward_tool_events_to_tui):
ToolEvent::Started→AgentEvent::ToolStartToolEvent::Completed→AgentEvent::ToolOutput
This task is spawned in run_tui_agent (line 81) and reads from an UnboundedReceiver<ToolEvent> that the shell executor publishes to (wired in src/agent_setup.rs lines 321–324 when with_tool_events = true, which is only set in TUI mode).
Both paths write into the same mpsc::Sender<AgentEvent> (clones of agent_tx created in src/channel.rs lines 189–196). The TUI loop in crates/zeph-tui/src/lib.rs reads from the corresponding receiver and calls handle_agent_event — so every tool execution triggers two ToolStart + two ToolOutput events.
Evidence from Logs
From ~/Library/Application Support/zeph/logs/zeph.2026-03-22.log:
12:27:16.125403Z DEBUG zeph_tui::app: TUI ToolOutput event received tool_name=bash ... ← from forward_tool_events_to_tui (ToolEvent::Completed)
12:27:16.125414Z DEBUG zeph_tui::app: finalizing existing streaming Tool message
12:27:16.141943Z DEBUG zeph_tui::channel: TuiChannel::send_tool_output called tool_name=bash ... ← TuiChannel
12:27:16.141966Z DEBUG zeph_tui::app: TUI ToolOutput event received tool_name=bash ... ← duplicate
12:27:16.141970Z DEBUG zeph_tui::app: finalizing existing streaming Tool message
The first ToolOutput event arrives before TuiChannel::send_tool_output is logged, confirming it originates from the forward_tool_events_to_tui bridge.
Visual Symptom
Each tool call renders as two separate entries in the TUI chat, e.g.:
✓ bash $ cargo +nightly fmt --all -- --check 2>&1 || true 13:26
$ cargo +nightly fmt --all -- --check 2>&1 || true
(no output)
Intended Design
forward_tool_events_to_tui was introduced to provide real-time streaming output via ToolEvent::OutputChunk → AgentEvent::ToolOutputChunk. The Started and Completed variants should not be forwarded there because the Channel trait already handles them authoritatively.
Fix
In src/tui_bridge.rs::forward_tool_events_to_tui, skip ToolEvent::Started and ToolEvent::Completed — only forward ToolEvent::OutputChunk. The Channel trait's send_tool_start and send_tool_output (called from the agent loop) are the authoritative source for those events and include the correct diff, filter_stats, and kept_lines data.
// before
match event {
ToolEvent::Started { .. } => AgentEvent::ToolStart { .. }, // remove
ToolEvent::OutputChunk { .. } => AgentEvent::ToolOutputChunk { .. }, // keep
ToolEvent::Completed { .. } => AgentEvent::ToolOutput { .. }, // remove
}
// after
match event {
ToolEvent::Started { .. } | ToolEvent::Completed { .. } => continue,
ToolEvent::OutputChunk { .. } => AgentEvent::ToolOutputChunk { .. },
}Affected Files
src/tui_bridge.rs—forward_tool_events_to_tuifunctioncrates/zeph-tui/src/channel.rs—send_tool_start,send_tool_output(correct, no changes needed)src/agent_setup.rs— wirestool_event_txinto shell executor (no changes needed)
Severity
High — every tool call in TUI mode is visually duplicated, degrading UX significantly. Does not affect CLI or Telegram channels.