fix: prevent phantom (previous) sessions from lazy-resume fallback#584
Merged
fix: prevent phantom (previous) sessions from lazy-resume fallback#584
Conversation
When lazy-resume fails (session not found / corrupt / shutdown) and creates a fresh session, set RecoveredFromSessionId and add the old ID to _closedSessionIds. Without this, MergeSessionEntries sees two entries with the same name and renames the old one to '(previous)'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The reconnect handler's 'Session not found' and 'corrupted' fallback paths also create fresh sessions without setting RecoveredFromSessionId or adding the old ID to _closedSessionIds, causing the same phantom '(previous)' entries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Owner
Author
Code Review — PR #584: fix: prevent phantom (previous) sessions from lazy-resume fallbackReviewed by: 3 independent reviewers | CI: Final Re-Review (4 commits)Previous Finding Status
Test Quality Assessment4 well-constructed tests:
The 3 behavioral tests cover the merge logic regardless of which call site triggers it. The structural test serves as a supplementary invariant guard against regression — consistent with the project's convention of using structural tests as supplements to behavioral coverage. No New Issues FoundAll 3 reviewers confirmed no new bugs, regressions, security issues, or race conditions. Recommended Action: ✅ ApproveAll prior findings addressed. Code is correct, consistent, and well-tested. Ship it. |
… consistency Review findings addressed: 1. ??= → = on RecoveredFromSessionId: in double-recovery (A→B→C), ??= preserves stale ancestor A instead of immediate predecessor B, breaking CanSafelyDropSupersededGroupMoveEntry after restart. 2. TryAdd → indexer: matches every other _closedSessionIds write site in the codebase. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds 4 tests: 1. Merge with RecoveredFromSessionId + _closedSessionIds drops old entry 2. Double-recovery (A→B→C) with immediate predecessor drops correctly 3. Double-recovery with stale ancestor proves why ??= was wrong 4. Structural guard that EnsureSessionConnectedAsync uses = (not ??=) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.
What
When lazy-resume fails (CLI returns "Session not found" / corrupt / shutdown) and creates a fresh session, the old persisted entry was not being marked as superseded. On the next
SaveActiveSessionsToDisk,MergeSessionEntriesfound both old and new entries with the same display name and renamed the old one to"(previous)".Root cause
The lazy-resume fallback in
EnsureSessionConnectedAsynccreates a fresh session but didn't:RecoveredFromSessionId— soCanSafelyDropSupersededGroupMoveEntryreturned false_closedSessionIds— so the merge didn't filter it outFix
Two lines after the fresh session is created:
state.Info.RecoveredFromSessionId ??= sessionId— marks the new session as the successor_closedSessionIds.TryAdd(sessionId, 0)— ensures the merge drops the old entryReal-world trigger
Observed on session "34678": the CLI shut down the original session (
session.shutdownat 02:57 UTC). After app restart, lazy-resume got "Session not found", created a fresh session, but left the old entry on disk → "34678 (previous)" appeared.Testing
3335/3335 tests pass.