Repository navigation
fix: GIT-51 session recovery — reconciliation onto the scope resolver - #21
Merged
Merged
Conversation
UNVERIFIED WIP — provenance unconfirmed, do not build on. Found uncommitted in the working tree at the start of the GIT-67/63/69 repair sprint (CLI session 84765490, 2026-08-05). Committed verbatim, unreviewed and unmodified, solely to preserve it: 519 insertions that existed in exactly one place on one machine, with no stash copy. Adds session recovery after MCP server restart: - active-sessions.ts:219 adoptSessionForCurrentProcess() - session-state.ts:116 resolveCurrentSession() - session-state.ts:127 getCurrentSession() now delegates to it - wired at session-start.ts:711, session-close.ts:858 - tests in active-sessions.test.ts:420, session-state-recovery.test.ts Not verified: typecheck, build, and unit tests have not been run against this commit. Not reviewed. Author unknown — possibly an orphan from a prior session that ended before committing. Per sprint ruling: park here, build GIT-69's scope resolver on a clean base containing none of these lines, then rebase this branch onto it and reconcile as GIT-51 work against GIT-51's own acceptance criteria. Refs GIT-51, GIT-67, GIT-69 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ject conflict The two ruled fixes, applied on top of GIT-69's resolver. The WIP commit (cherry-picked as cb073d6) is unmodified beneath this; wip/git-51-session- recovery is left intact as the preserved artifact rather than rebased away. FIX (a) — the latch. As literally worded ("cleared on session_start") this was ALREADY satisfied: setCurrentSession and clearCurrentSession both reset recoveryAttempted, and every session_start path calls setCurrentSession. Verified before changing anything rather than implementing a redundant fix. The ruling's INTENT was not satisfied. The residual gap is narrower than my original report and worse in one specific case: the SessionStart hook runs as a SEPARATE CLI process (scar 55d1bccd), so a session can be registered AFTER this process has already tried and failed to recover. The boolean latch made that failure permanent for the process lifetime, and with GIT-67's R5 guard now refusing sessionless writes on pro, one early miss would refuse every subsequent write — a fail-closed bug replacing the fail-open one we just fixed. The latch is now keyed on the registry's mtime fingerprint (getRegistryFingerprint). Nothing changed → no re-read, preserving the I/O-avoidance the boolean was for. Registry touched by anyone → retry. FIX (b) — project precedence. `data.project || entry.project` was a silent two-source fallback: when session.json and the registry disagreed, session.json won and nothing said so. GIT-69's scope resolver keys on (project, sessionId), so a wrong value there scopes an entire session to the wrong namespace — the cross-project leak class through a new door. Precedence is unchanged and now documented (session.json is authoritative; the registry is a lagging index). What changed is that a conflict is detected, logged loudly, and carried on the context as recoveryConflict so a consumer can see its scope rests on a resolved conflict rather than on agreement. Tests: +5 (recovery finds a session registered after an earlier failure; latch still suppresses re-reads while unchanged; conflict flagged; session.json wins; no false flag when the two agree). 1088 unit + 12 e2e green, typecheck and build clean. Refs GIT-51, GIT-69, GIT-67, 55d1bccd Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nTEG-dev
changed the base branch from
feature/GIT-69-thread-scope-resolver
to
main
August 7, 2026 02:24
nTEG-dev
marked this pull request as ready for review
August 7, 2026 02:24
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.
Draft — verification only. Do not merge. Base is
feature/GIT-69-thread-scope-resolver, notmain.Based on the resolver branch deliberately: against
mainthe diff would show all of GIT-69 + GIT-67/63 stacked underneath and the matrix would re-verify what PR #20 already covers. Based here, the diff is only the GIT-51 delta and reviews as exactly the reconciliation it is. Retarget tomainwhen #20 merges.What this is
The GIT-51 session-recovery implementation, found uncommitted in the working tree at the start of the repair sprint and adopted as this ticket's implementation candidate. Author identified as CLI session
5b2c9f49(Jul 28–29) from transcript evidence.cb073d6is that work cherry-picked unmodified.wip/git-51-session-recovery@8d70610is left untouched as the preserved artifact — the original is evidence, and evidence does not get rewritten to make integration cleaner.4ac3e2eapplies the two ruled fixes on top.Fix (a) — recovery latch, keyed on the registry rather than a boolean
As literally worded ("clear
recoveryAttemptedonsession_start") this was already satisfied:setCurrentSessionandclearCurrentSessionboth reset it, and everysession_startpath callssetCurrentSession. Verified before changing anything.The ruling's intent was not satisfied. The SessionStart hook runs as a separate CLI process (scar
55d1bccd), so a session can be registered after this process has already tried and failed to recover. The boolean latch made that failure permanent for the process lifetime — and with GIT-67's R5 guard now refusing sessionless writes on pro, one early miss would refuse every subsequent write. A fail-closed bug replacing the fail-open one the sprint just fixed, produced by two of our own fixes interacting.The latch is now keyed on the registry's mtime fingerprint. Nothing changed → no re-read, preserving the I/O-avoidance the boolean existed for. Registry touched by any process → retry.
Fix (b) — project precedence stated rather than silent
data.project || entry.projectwas a two-source fallback: whensession.jsonand the registry disagreed,session.jsonwon and nothing said so. GIT-69's scope resolver keys on(project, sessionId), so a wrong value there scopes an entire session to the wrong namespace — the cross-project leak class arriving through a new door.Precedence is unchanged and now documented (
session.jsonauthoritative, registry a lagging index). What changed is that the conflict is detected, logged loudly, and carried asrecoveryConflicton the context. The defect was never the choice — it was that the choice was invisible.Verification
npm run typechecknpm run buildnpm run test:unitNew coverage (+5): recovery finds a session registered after an earlier failure; the latch still suppresses re-reads while unchanged; conflict flagged;
session.jsonwins; no false flag when the two agree.Not in scope
parent_sessionlineage is GIT-72, deliberately not built here. No lineage is synthesized fromSessionChild, which records roles inside one session and has nosession_id.Refs GIT-51, GIT-69, GIT-67, GIT-72