Skip to content

bug(tui): tool calls duplicated in TUI mode — ToolStart and ToolOutput sent twice per tool execution #2116

@bug-ops

Description

@bug-ops

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_startAgentEvent::ToolStart
  • TuiChannel::send_tool_outputAgentEvent::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::StartedAgentEvent::ToolStart
  • ToolEvent::CompletedAgentEvent::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::OutputChunkAgentEvent::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.rsforward_tool_events_to_tui function
  • crates/zeph-tui/src/channel.rssend_tool_start, send_tool_output (correct, no changes needed)
  • src/agent_setup.rs — wires tool_event_tx into 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions