Skip to content

fix: GIT-51 session recovery — reconciliation onto the scope resolver - #21

Merged
nTEG-dev merged 2 commits into
mainfrom
feature/GIT-51-session-recovery
Aug 7, 2026
Merged

nTEG-dev merged 2 commits into
mainfrom
feature/GIT-51-session-recovery

Conversation

@nTEG-dev

@nTEG-dev nTEG-dev commented Aug 6, 2026

Copy link
Copy Markdown
Member

Draft — verification only. Do not merge. Base is feature/GIT-69-thread-scope-resolver, not main.

Based on the resolver branch deliberately: against main the 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 to main when #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.

cb073d6 is that work cherry-picked unmodified. wip/git-51-session-recovery @ 8d70610 is left untouched as the preserved artifact — the original is evidence, and evidence does not get rewritten to make integration cleaner.

4ac3e2e applies the two ruled fixes on top.

Fix (a) — recovery latch, keyed on the registry rather than a boolean

As literally worded ("clear recoveryAttempted on session_start") this was already satisfied: setCurrentSession and clearCurrentSession both reset it, and every session_start path calls setCurrentSession. 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.project was a 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 arriving through a new door.

Precedence is unchanged and now documented (session.json authoritative, registry a lagging index). What changed is that the conflict is detected, logged loudly, and carried as recoveryConflict on the context. The defect was never the choice — it was that the choice was invisible.

Verification

Check Result
npm run typecheck clean
npm run build clean
npm run test:unit 1088 passed
e2e GIT-67/63 acceptance 12 passed

New coverage (+5): recovery finds a session registered after an earlier failure; the latch still suppresses re-reads while unchanged; conflict flagged; session.json wins; no false flag when the two agree.

Not in scope

parent_session lineage is GIT-72, deliberately not built here. No lineage is synthesized from SessionChild, which records roles inside one session and has no session_id.

Refs GIT-51, GIT-69, GIT-67, GIT-72

Claude and others added 2 commits August 5, 2026 23:25
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
nTEG-dev changed the base branch from feature/GIT-69-thread-scope-resolver to main August 7, 2026 02:24
@nTEG-dev
nTEG-dev marked this pull request as ready for review August 7, 2026 02:24
@nTEG-dev nTEG-dev closed this Aug 7, 2026
@nTEG-dev nTEG-dev reopened this Aug 7, 2026
@nTEG-dev
nTEG-dev merged commit 72bde42 into main Aug 7, 2026
4 checks passed
@nTEG-dev
nTEG-dev deleted the feature/GIT-51-session-recovery branch September 23, 2026 05:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant