Skip to content

fix: send only stdout delta in Bash terminal output to prevent replay#14

Merged
william0wang merged 1 commit into
mainfrom
chore/fix-terminal-dup
Jul 10, 2026
Merged

fix: send only stdout delta in Bash terminal output to prevent replay#14
william0wang merged 1 commit into
mainfrom
chore/fix-terminal-dup

Conversation

@william0wang

Copy link
Copy Markdown
Owner

Problem

Long-running Bash commands display duplicated output. The longer the command runs, the worse the duplication.

Root cause is a semantic mismatch:

  • zcode's stdoutTail (progress events) and result content are cumulative snapshots — each carries the full tail of stdout so far.
  • ACP's terminal_output.data is append semantics — the client writes each value as new bytes to the terminal.

dispatchTerminalUpdate passed the full snapshot into data every time, so N progress events replayed the output N times, and the final result event appended it once more.

Fix

Add terminalSentData: Map<callId, string> on ZcodeAcpServer to track the last-sent snapshot per Bash call. dispatchTerminalUpdate now:

  1. Diffs the new snapshot against the last-sent one and emits only the suffix delta (full.slice(lastSent.length))
  2. Falls back to full output when the snapshot isn't monotonic (tail rotation / prefix mismatch) to avoid data loss
  3. Clears the callId entry on completed/failed

Test

  • Added regression tests: repeated cumulative snapshots emit only deltas; result after progress emits only the unsent tail
  • All 154 tests pass
    `

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements delta deduplication for terminal output updates by tracking the last-sent stdout snapshot per callId and emitting only the new suffix. This prevents long-running commands from replaying their entire output multiple times. The feedback highlights a critical edge case: when the stdout tail buffer rotates, the current prefix check fails and falls back to sending the full snapshot, causing output duplication. It is recommended to resolve this by finding the overlap between the consecutive snapshots and to add a corresponding regression test.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/handlers/dispatch.ts
// Only the suffix past the previously-sent snapshot is new output. Guard
// against a non-monotonic snapshot (tail rotated) by falling back to the full
// value when the previous snapshot isn't a prefix of the current one.
const delta = lastSent && full.startsWith(lastSent) ? full.slice(lastSent.length) : full;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When the stdout tail buffer rotates (i.e., older output is discarded from the beginning of the cumulative snapshot), full.startsWith(lastSent) will be false. In this case, falling back to the full full snapshot causes the remaining overlapping part of the buffer to be replayed and duplicated in the terminal. Since long-running commands are exactly the ones that will trigger tail rotation, this defeats the purpose of preventing replay.

We can resolve this by finding the longest suffix of lastSent that matches a prefix of full (the overlap) and sending only the remaining suffix of full as the delta.

  let delta = full;
  if (lastSent) {
    if (full.startsWith(lastSent)) {
      delta = full.slice(lastSent.length);
    } else {
      const maxOverlap = Math.min(lastSent.length, full.length);
      for (let i = maxOverlap; i > 0; i--) {
        if (lastSent.endsWith(full.slice(0, i))) {
          delta = full.slice(i);
          break;
        }
      }
    }
  }

Comment thread tests/dispatch.test.ts
@william0wang william0wang merged commit e0b4695 into main Jul 10, 2026
1 check passed
@william0wang william0wang deleted the chore/fix-terminal-dup branch July 10, 2026 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant