agentHost: retroactively recognise unmarked crash orphans - #336806
Open
Sandeep Somavarapu (sandy081) wants to merge 1 commit into
Open
Sandeep Somavarapu (sandy081) wants to merge 1 commit into
Sandeep Somavarapu (sandy081) wants to merge 1 commit into
Conversation
During catalog reconciliation, retroactively marks provider-unresolvable sessions as provisional only when their local conversation storage is empty. Listing then reuses the existing provisional suppression path without per-listing storage opens. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 26730bc1-5d8d-48f1-a4c6-bde566082230
Copilot started reviewing on behalf of
Sandeep Somavarapu (sandy081)
September 18, 2026 17:41
View session
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The provisional marker can be reintroduced by stale reconciliation after concurrent mutation or materialization, potentially hiding a real session.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Retroactively identifies and suppresses legacy crash-orphaned Agent Host sessions when local storage contains no conversation turns.
Changes:
- Detects persisted and local conversation turns.
- Marks empty, source-unresolvable sessions as provisional.
- Adds regression and safety-path coverage.
| File | Description |
|---|---|
sessionDatabase.test.ts |
Tests conversation-turn detection. |
agentService.test.ts |
Tests orphan recognition and safety guards. |
sessionTestHelpers.ts |
Tracks turns in test databases. |
sessionDatabase.ts |
Adds the turn-existence query. |
agentService.ts |
Mirrors retroactive markers and guards provider availability. |
agentHostCatalogReconciliationService.ts |
Marks empty unresolved sessions as provisional. |
sessionDataService.ts |
Adds the database interface method. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+368
to
+369
| await this._catalogDatabase.setSessionProvisional(sessionKey, true); | ||
| this._onDidMarkSessionProvisional(sessionKey); |
Comment on lines
+5411
to
+5412
| // The empty local store now proves the placeholder is safe to hide | ||
| // even before the marker read completes. |
roblourens
approved these changes
Sep 18, 2026
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.


Follow-up to #336480. Colleagues who hit the phantom-session bug before that fix shipped still see the rows, and always will — #336480 is prevention-only, and a session orphaned before it landed carries no marker for suppression to act on.
Evidence
From a reporter's logs, the row
copilotcli:/e50ba1ea-84a3-4728-9093-15c58e9330ae:Provider copilotcli is not ready to open …— the non-provisional branch, which only runs when the session has no marker.sessions_v2 import for provider copilotcli … marker setat 15:02:14, and the failure fired at 15:19:54.is ineligiblethat run (matching the 27 fallbacks in651 catalog-served, 27 provider fallback), and this session is not among them.So the catalog serves stale metadata written at creation for a backing that never materialized, and nothing will ever retract it.
The change
Reconciliation already visits exactly this population. #336434 added the
sourceUnresolvableoutcome for a session whose provider is registered but cannot describe it, and parks it. At that point this change adds the missing evidence:If the session's local storage holds no conversation turns, write the durable
sessionProvisional:marker retroactively.The next listing then suppresses it through the path #336480 already ships, which filters the assembled result and so covers catalog-served rows. A pre-fix orphan is converted into exactly the shape the existing fix handles.
Why here rather than in listing: doing this at listing would mean a provider round-trip and a storage open for every catalog-served row — ~651 of them in the logs above — which is precisely the cost the catalog exists to avoid. Reconciliation visits each session once and then parks it, so the probe does not repeat.
Cost: zero extra opens per listing. Reconciliation reuses the database reference it has already acquired; the only new work is one turn-existence query, and only for source-unresolvable sessions.
Why this is safe without a marker
Suppression still requires positive evidence that the session is empty. A session with any conversation turn is never marked, so real user content cannot be hidden. The existing guards are unchanged: the provider's catalog must be established readable before a miss counts as authoritative, a provider that throws or is unavailable is never treated as evidence of absence, and anything uncertain fails open. Under-suppressing costs a junk row; over-suppressing costs a user's work.
Nothing is deleted. The row stays on disk and reappears if the provider ever vouches for it again.
Tests
The repro matches the reported shape — a catalog-served, unmarked orphan with empty local storage — and fails without the fix:
Guards alongside it: a catalog-served session with turns is never marked or suppressed; an unavailable provider is never treated as evidence; the marked-orphan fast path still works without consulting local storage.
5,422 passing across the agentHost node suites, typecheck and eslint clean.
Scope
Prevention plus retroactive recognition; still no deletion. Affected users need no action — the row disappears on its own once reconciliation has visited it.