Skip to content

Commit 1dec635

Browse files
authored
Handle PTY overflow parser carry safely (#198)
* Handle PTY overflow parser carry safely * Preserve PTY write order on actor fallback * Fix PTY write rollback and overflow escape recovery * Fix flaky state watcher registry test * Document parser carry reconnect invariants
1 parent 1059385 commit 1dec635

20 files changed

+1291
-281
lines changed

internal/app/state_watcher_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func TestStateWatcher_NotifiesOnRegistryWrite(t *testing.T) {
108108

109109
var notified string
110110
var mu sync.Mutex
111-
done := make(chan struct{})
111+
done := make(chan struct{}, 1)
112112

113113
sw, err := newStateWatcher(registryPath, "", func(reason string, paths []string) {
114114
mu.Lock()

internal/ui/center/model_input_lifecycle.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ func (m *Model) updatePtyTabReattachResult(msg ptyTabReattachResult) (*Model, te
7979
createdTerminal = true
8080
}
8181
if tab.Terminal != nil {
82+
// Do not reset parser state when reusing an existing terminal here.
83+
// pendingOutput may still contain continuation bytes queued under the
84+
// current parser carry, and reconnect must preserve that continuity until
85+
// buffered output is explicitly reconciled.
8286
tab.Terminal.AllowAltScreenScrollback = true
8387
m.applyTerminalCursorPolicyLocked(tab)
8488
if createdTerminal || len(tab.Terminal.Scrollback) == 0 {
@@ -91,6 +95,13 @@ func (m *Model) updatePtyTabReattachResult(msg ptyTabReattachResult) (*Model, te
9195
tab.reattachInFlight = false
9296
tab.Running = true
9397
resetChatCursorActivityStateLocked(tab)
98+
tab.parserResetPending = false
99+
tab.actorWritesPending = 0
100+
tab.actorWriteEpoch++
101+
tab.overflowTrimCarry = vterm.ParserCarryState{}
102+
tab.ptyNoiseTrailing = nil
103+
tab.actorQueuedNoiseTrailing = tab.actorQueuedNoiseTrailing[:0]
104+
tab.actorQueuedCarry = tab.Terminal.ParserCarryState()
94105
tab.bootstrapActivity = true
95106
tab.bootstrapLastOutputAt = time.Now()
96107
tab.mu.Unlock()

0 commit comments

Comments
 (0)