Fix log/ring merge splicing non-contiguous bytes on log lag (BUG-076) - #919
Merged
Conversation
readLiveRebuildHistory's merge assumes the ring's overflow tail picks up exactly where the log-covered prefix ends. When the on-disk log lags the ring by more than the ring's own 256KB capacity — rare under normal operation, but reachable under a heavy output burst that outpaces a momentarily-slow disk write — the old code clamped the overflow and concatenated the log prefix directly with the ring's tail anyway. The two ranges are NOT contiguous in that case: the bytes in between were evicted from the ring before the log could catch up, and are unrecoverable from either source right now. Splicing them together feeds x/vt content whose escape sequences and cursor state were never actually adjacent, producing unexplained missing characters/words and garbled symbols at the seam — on an actively-streaming pane with no bind/resize event at all, easy to mistake for BUG-075 (same "no bind involved" signature, different mechanism: BUG-075 was a plain incremental feed with no log involved). Fix: when the gap is unrecoverable, return just the log-covered prefix and its true total (logSize, not ringTotal). The caller records this as emuFedTotal, so understating it defers the unrecoverable range to the next Draw's ring-wrap check, which naturally retries the exact catch-up (readLogRangeForTask, BUG-073) once the log has caught up, instead of permanently losing content and mis-splicing what was captured. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
Merging this branch will increase overall coverage
Coverage by fileChanged files (no unit tests)
Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code. Changed unit test files
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Same investigation as #912/#913/#916. After #916 (BUG-075) shipped, the coordinator reported a recurrence with a similar-looking but distinct signature: dropped letters mid-word and stray inserted characters (e.g. a "%" appearing where a letter should be), on an actively-streaming coordinator pane doing heavy git/tag operations, with no bind/resize/navigation event involved. Per the coordinator's explicit ask, I verified the running dogfood binary still contained #916's fix (confirmed via the binary's embedded VCS revision being a descendant of the BUG-075 commit, and by diffing the source at that exact revision) before looking for a second, distinct cause — grepping the codebase for other unsynchronized
TotalWritten()/RecentOutput()call-site pairs.Root cause
readLiveRebuildHistory's log/ring merge assumes the ring's overflow tail picks up exactly where the log-covered prefix ends. When the on-disk log lags the ring by more than the ring's own 256KB capacity — rare under normal operation (readLoopflushes log writes chunk-by-chunk), but reachable under a heavy output burst that outpaces a momentarily-slow disk write (matching the reported scenario: bulk tag/archive operations streaming a lot of output quickly) — the old code clamped the overflow and concatenated the log-covered prefix directly with the ring's overflow tail anyway. The two ranges are not contiguous in that case: the bytes in between were evicted from the ring before the log could catch up, and are unrecoverable from either source right now. The function's own comment already named this ("bytes... are unrecoverable") but the code spliced the two chunks together regardless, feeding x/vt content whose escape sequences and cursor state were never actually adjacent — producing exactly the reported signature (missing characters/words, garbled symbols at the seam).This is a genuinely distinct root cause from #916 (BUG-075 was a plain incremental feed with no on-disk log involved at all) that happens to share the same "no bind event" surface signature, which is why it was easy to mistake for a #916 regression.
Fix
When the gap is unrecoverable, return just the log-covered prefix and its true total (
logSize, notringTotal). The caller records this asemuFedTotal, so understating it defers the unrecoverable range to the next Draw's ring-wrap check, which naturally retries the exact catch-up (readLogRangeForTask, #912/BUG-073) once the log has had a chance to catch up — instead of permanently losing content and mis-splicing what little was captured.Test plan
TestReadLiveRebuildHistory_UnrecoverableGapDoesNotSpliceNonContiguousBytes— new test constructing exactly this scenario (log covers[0,4), ring currently holds only bytes[8,10), bytes[4,8)genuinely gone). Verified it fails on the pre-fix code (len(raw)=6, total=10— the old code'sraw/totalmismatch, proving the caller would be lied to about what was actually fed).TestReadLiveRebuildHistory_LogTailOnly/OverflowMerge/NoLogFallback/NilSessionpass unchanged (no behavior change for the non-clamped/normal cases).go test ./internal/tui/terminal/... -race -count=1green.make pre-prclean (same pre-existingvuln-gate exception as prior PRs in this series).context/knowledge/gotchas/pty-terminal.mdupdated (BUG-076).origin/master+ cherry-picked just this commit, one trivial textual conflict in the shared gotcha-index table row, resolved manually).🤖 Generated with Claude Code
Co-authored-by: Claude noreply@anthropic.com