Skip to content

fix(harness): discard the delta coalescer's held text on a provider-retry restart - #6668

Merged
pedrofrxncx merged 1 commit into
mainfrom
fix/coalescer-discard-on-provider-retry
Aug 28, 2026
Merged

fix(harness): discard the delta coalescer's held text on a provider-retry restart#6668
pedrofrxncx merged 1 commit into
mainfrom
fix/coalescer-discard-on-provider-retry

Conversation

@pedrofrxncx

@pedrofrxncx pedrofrxncx commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Follows #6664 (token-level streaming, merged today), hardening the retry path it added.

On a transient provider rejection, runClaudeCode restarts the attempt and clears pending (pending.length = 0) since the dead attempt's buffered chunks aren't part of the next one. The createDeltaCoalescer added in the same PR can independently be holding an un-flushed text/reasoning delta at that exact point (deltas can accumulate before the turn's first assistant message, i.e. before started flips — the same window canRestartCleanly() checks) — nothing cleared it. A later drain() on the new attempt would then splice the dead attempt's partial, abandoned text into the new attempt's stream, or worse, merge it into an unrelated delta if the new attempt reuses the same block id.

Failure scenario: provider rejects the request mid-stream after emitting a partial (<200 char) text delta that never got flushed; the retry restarts a fresh SDK session, and the abandoned delta text reappears prefixed onto (or merged into) the first delta of the successful retry.

Fix: added discard() to the coalescer (drops the held delta without emitting it) and call it right next to pending.length = 0 in the transient-provider-rejection retry branch.

Regression test: discard drops what is held instead of emitting it in packages/harness-runner/claude-code.test.ts — pushes a delta, discards, drains (expects empty), then verifies a fresh push after discard is not merged with the discarded text.

To confirm: bun test packages/harness-runner/claude-code.test.ts.

Locally ran: bun run fmt, bunx tsc --noEmit (packages/harness-runner), the targeted test file above (39 pass), and bunx oxlint on both changed files (0 warnings/errors). Full CI validates the rest.


Summary by cubic

Fixes the delta coalescer retaining abandoned text across provider-retry restarts, which could splice a dead attempt's partial delta into the new attempt's stream or merge it with an unrelated delta.

  • Adds discard() to createDeltaCoalescer and calls it alongside the pending reset during retry.
  • Includes a regression test verifying discarded text is not emitted or merged.

Written for commit e496ce8. Summary will update on new commits.

Review in cubic

…etry restart

buildOptions/runClaudeCode already drops `pending` when a transient
provider rejection restarts the attempt, since a dead attempt's buffered
chunks aren't part of the next one. The delta coalescer added alongside it
(#6664) can independently be holding an un-flushed text/reasoning delta at
that same point — nothing cleared it, so a later drain() would splice the
dead attempt's partial text into the new attempt's stream, or merge it into
an unrelated delta on an id that gets reused.

Added discard() to the coalescer and call it next to the pending.length = 0
reset.
@pedrofrxncx
pedrofrxncx enabled auto-merge (squash) August 28, 2026 12:05
@pedrofrxncx
pedrofrxncx merged commit 2b22e76 into main Aug 28, 2026
33 checks passed
@pedrofrxncx
pedrofrxncx deleted the fix/coalescer-discard-on-provider-retry branch August 28, 2026 12:06
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