Conversation
…ents source Three fixes for the recurring 'AndroidShellHandler loses history on restart' bug: 1. ResumeSessionAsync: detect when SDK returns a different session ID than requested (actualSessionId != sessionId). When this happens, copy events to the new directory and use the actual ID. Previously the input ID was always stored, causing events to be written to a directory that doesn't match what active-sessions.json tracks. 2. Fallback restore path: use FindBestEventsSource() to scan session directories for the one with the most events matching the working directory, instead of always loading from the stale entry.SessionId. This recovers accumulated history from intermediate sessions created during previous fallback cycles. 3. Extract CopyEventsToNewSession as a shared helper (was inline in the fallback path). Now used by ResumeSessionAsync, the reconnect path, and the fallback restore path. Root cause: When active-sessions.json has a stale session ID (from before PR #378's flush fix), each restart creates a new session via fallback. LoadHistoryFromDisk reads from the stale ID's events.jsonl (517 lines) instead of the most recent intermediate session (10,488 lines). The user sees the same old history every time, losing all work done since. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ew tests Fixes from code review on PR #382: 1. FALSE POSITIVE PATH MATCHING (High): Replace yaml.Contains() with exact cwd line parsing via WorkspaceYamlMatchesCwd(). Prevents '/project' from matching '/project-2' in workspace.yaml. 2. PERFORMANCE (Medium): Limit FindBestEventsSource to scan at most MaxSessionDirsToScan (200) directories, sorted newest-first via OrderByDescending(LastWriteTimeUtc). 3. TEMP FILE LEAK (Low): Declare tmpFile outside try block in CopyEventsToNewSession; clean up in catch if File.Move fails. 4. REDUNDANT FALLBACK (Low): Simplify SessionId assignment from 'actualSessionId ?? sessionId' to just 'sessionId' since sessionId is already reassigned to actualSessionId in the mismatch case. 5. RACE CONDITION (Medium): Added documentation that CopyEventsToNewSession runs before event handler registration, minimizing the race window with SDK event writes. New tests (10): - FindBestEventsSource_DoesNotMatchSubstringPaths - FindBestEventsSource_HandlesEmptyEventsJsonl - WorkspaceYamlMatchesCwd_ExactMatch - WorkspaceYamlMatchesCwd_RejectsSubstringMatch - WorkspaceYamlMatchesCwd_CaseInsensitive - WorkspaceYamlMatchesCwd_ReturnsFalse_WhenNoCwdLine - WorkspaceYamlMatchesCwd_HandlesQuotedValue - FindBestEventsSource_LimitsDirectoryScan (structural) - CopyEventsToNewSession_CleansTempFileOnFailure (structural) - Updated ResumeSessionAsync structural test for simplified fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PR #382 Review — Multi-Model Consensus ReportCI: No checks configured Consensus Findings (2+ models)🟡 MODERATE — — Race in
|
Bug
The 'AndroidShellHandler' session loses all history on every PolyPilot restart. 181 messages show up, but they're always the same stale set from Mar 13 — work done since then is lost.
Root Cause (follows PR #378)
PR #378 fixed the missing flush after restore, but there are two additional issues:
Session ID mismatch in ResumeSessionAsync: Line 1618 stored
sessionId(the input) instead ofcopilotSession.SessionId(the actual ID returned by the SDK). If the persistent server returns a different session ID, events get written to the wrong directory.Stale events source in fallback path: When
active-sessions.jsonpoints to an old session ID (before PR fix: flush active-sessions.json after restore to persist recreated session IDs #378 flush fix), the fallback always loads from that stale ID's events.jsonl (517 lines), ignoring intermediate sessions that accumulated more history (10,488+ lines).Fix
ResumeSessionAsync: Detect when
copilotSession.SessionId != sessionId, copy events to the new directory, and use the actual session ID going forward.FindBestEventsSource(): New method that scans session directories matching the working directory and picks the one with the most events. Used by the fallback restore path instead of blindly loading from the stale entry.
CopyEventsToNewSession(): Extracted from the inline copy logic into a shared helper, now used by ResumeSessionAsync, the reconnect path, and the fallback restore path.
Tests
FindBestEventsSource(correct session found, different cwd ignored, null cwd, no workspace.yaml)