Repository navigation
fix(serve): Repair persisted session lifecycle #9626
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
doudouOUC
merged 18 commits into
QwenLM:main
from
doudouOUC:fix/session-storage-lifecycle-9488
Aug 25, 2026
Merged
Changes from 14 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
3c1bdf4
fix(serve): repair persisted session lifecycle
doudouOUC 371ea3b
codex: fix CI failure on PR #9626
doudouOUC 13ebb8b
codex: fix CI failure on PR #9626
doudouOUC ab734d2
codex: address PR review feedback (#9626)
doudouOUC f6bb889
codex: address PR review feedback (#9626)
doudouOUC a6367f7
codex: address PR review feedback (#9626)
doudouOUC 9117c3a
codex: address PR review feedback (#9626)
doudouOUC 0ab809a
codex: address PR review feedback (#9626)
doudouOUC e9b3c91
codex: address PR review feedback (#9626)
doudouOUC 11ef22d
codex: address PR review feedback (#9626)
doudouOUC d49ea59
codex: address PR review feedback (#9626)
doudouOUC 9c1b867
codex: address PR review feedback (#9626)
doudouOUC b7329fd
codex: address PR review feedback (#9626)
doudouOUC 60118c3
codex: address PR review feedback (#9626)
doudouOUC 1b2c9f3
chore: merge latest main into PR #9626
doudouOUC 21da77c
codex: address PR review feedback (#9626)
doudouOUC 782dd86
codex: address PR review feedback (#9626)
doudouOUC 49b67eb
chore: merge latest main into PR #9626
doudouOUC File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
65 changes: 65 additions & 0 deletions
65
docs/design/2026-08-22-session-storage-lifecycle-repair.md
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| # Session storage lifecycle repair | ||
|
|
||
| ## Problem | ||
|
|
||
| Session lifecycle operations currently depend on loading a transcript as a usable conversation. That is too strong for delete, archive, and unarchive. A regular transcript file can still need maintenance when it is empty, has a torn or malformed head, or belongs to a legacy child whose parent no longer exists. | ||
|
|
||
| This leaves physical files stranded even though the selected workspace and archive state are unambiguous. | ||
|
|
||
| ## Scope | ||
|
|
||
| This change makes delete, archive, and unarchive operate on exact-spelling regular transcript files owned by the selected workspace. It also adds an explicit repair option for an active/archive conflict. | ||
|
|
||
| The change does not alter transcript loading, listing, export, organization, Live session identity, or general case-insensitive lookup. Normal generated session UUIDs remain canonical lowercase values. | ||
|
|
||
| ## Storage classification | ||
|
|
||
| The lifecycle classifier inspects the requested UUID file in the active and archived directories. | ||
|
|
||
| - A missing path contributes no state. | ||
| - A regular empty or damaged file contributes physical state. | ||
| - A readable first record must identify the requested session and the selected workspace. | ||
| - A readable record that identifies another workspace is treated as not found. | ||
| - A symlink, directory, or other non-regular entry fails closed. | ||
| - Active and archived files together form a conflict. | ||
|
|
||
| The classifier captures file identity for physical-only maintenance. Immediately before unlink or rename, Core verifies that the same regular files still occupy the same paths. | ||
|
|
||
| ## Routing | ||
|
|
||
| Workspace-qualified routes mutate only the selected trusted runtime. | ||
|
|
||
| Workspace-less ordinary routes retain their existing primary-workspace behavior. Internal Conversations runtimes may be selected only when the requested batch has one unambiguous owner. Runtime selection is repeated while holding the lifecycle coordinator lock; a changed or unavailable generation returns the existing retryable runtime error instead of mutating stale storage. | ||
|
|
||
| Provenance remains authoritative for loading and listing, but it does not prevent maintenance of an otherwise owned regular transcript. This permits cleanup of legacy orphan children without making them loadable. | ||
|
|
||
| ## Conflict repair | ||
|
|
||
| Archive and unarchive keep the non-destructive default behavior: an active/archive conflict is returned as a per-session error and neither persisted copy is moved, removed, or overwritten. Archive still strictly closes a live session before classifying the conflict, so queued records may be flushed to the active transcript. The batch lifecycle routes, including workspace-qualified routes, return that outcome in a `200` response instead of the earlier workspace-qualified `409 session_conflict` envelope. | ||
|
|
||
| Callers may send `resolveConflicts: true`: | ||
|
|
||
| - archive keeps the archived transcript and removes the active transcript; | ||
| - unarchive keeps the active transcript and removes the archived transcript. | ||
|
|
||
| The response adds `resolvedConflicts`, containing IDs repaired by that request. Delete keeps its existing compatibility behavior and removes both copies without requiring an option. | ||
|
|
||
| ## Writer and generation safety | ||
|
|
||
| Daemon maintenance runs under the per-session lifecycle coordinator and writer lock. Damaged transcripts use the same writer lock with a maintenance sentinel because the normal writer proof intentionally rejects torn JSONL. The sentinel does not bypass a certified handoff: if transcript bytes change after a writer seals its lock, takeover continues to fail closed with `SessionTranscriptChangedError` and requires operator intervention. Core still snapshots and verifies the actual transcript paths before committing the mutation. | ||
|
|
||
| The selected runtime generation is checked after coordinator acquisition, before the filesystem commit, and after the operation. A generation replacement therefore cannot silently redirect an in-flight mutation to another runtime. | ||
|
|
||
| ## API compatibility | ||
|
|
||
| The REST and ACP archive/unarchive request bodies accept the optional boolean `resolveConflicts`. Omitting it preserves the non-mutating conflict behavior, while the workspace-qualified REST transport now reports the conflict in the batch result as described above. Non-boolean values are invalid requests. | ||
|
|
||
| Archive and unarchive responses add `resolvedConflicts`. SDK result types keep that field optional so clients remain compatible with older daemons, while new daemons always return the array. Existing SDK client-ID call forms remain valid; options use an additional overload position. | ||
|
|
||
| The `session_storage_conflict_repair` capability advertises support for the option and response field. | ||
|
|
||
| ## Verification | ||
|
|
||
| The regression matrix covers empty, damaged, and legacy-orphan transcripts across delete, archive, and unarchive in Core, workspace-qualified internal routes, and unqualified unique-owner routing. Each success asserts the final active/archive file state, and moves preserve the original bytes. | ||
|
|
||
| Compatibility tests retain default archive and unarchive conflict behavior and delete-both behavior. Additional tests cover explicit repair in both directions, foreign or ambiguous ownership rejection, invalid option types, SDK request bodies, generation fencing, and replacement detection. |
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
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
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
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
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
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
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deferring after independent triage: this is a documentation clarification, not a Critical correctness or data-loss defect. Runtime behavior already fails closed and is covered by the mixed local/foreign ownership tests. This PR is in review round 10 and is restricted to Critical fixes, so the prose clarification should be handled separately.