Skip to content

fix: never resume a session of another project (GIT-86) - #37

Merged
2 commits merged into
mainfrom
bugfix/GIT-86-cross-project-resume
Sep 22, 2026
Merged

2 commits merged into
mainfrom
bugfix/GIT-86-cross-project-resume

Conversation

@nTEG-dev

Copy link
Copy Markdown
Member

What

session_start never resumes, displaces or supersedes a session of another project (GIT-86).

  • findSessionByHostPid and findResumableSessionOnDisk take the requested project and only return a session of that project.
  • project passed, no match: a new session starts. The registry slot is now hostname + pid + project, so the other project's session stays registered, is not marked superseded, and pruneOrphanedDirs no longer treats its directory as unregistered.
  • project absent: the most recent session is resumed, and the display says which project it is in: Resumed project: <p> (no project was passed — pass project to start a session in another). The result also carries project_from_resumed_session: true.
  • force: true: startedAt, surfaced scars, scar confirmations, observations and children carry forward only from a session of the same project. A session of another project is left open.
  • session_close's registry fallback now looks the session up by id instead of by pid.

Why

One desktop process serves every chat, so hostname + pid doesn't identify a session. Before this change, session_start(Y) found the open X session by pid, replaced the caller's project with X's (logged to stderr only) and carried on in X. Reported twice.

The obvious fix (filter the two lookups) isn't enough on its own. registerSession would still displace X from the registry and mark it superseded in Supabase, and X's directory would then be deleted as an orphan an hour later. So X wouldn't actually be resumable.

Found along the way (same code path, fixed here)

  • Scar confirmations were never carried on force: true: setCurrentSession always reset them. So surfaced scars came across without their confirmations.
  • The free tier received the carried startedAt but only used it when resuming, so a forced free-tier session restarted its duration clock.

How to test

Standing rule: the venue runs the v1.8.0 setup.sql first. The venue vujndhcumgroztfkkdus was reset and provisioned from v1.8.0:schema/setup.sql (byte-identical to the tag) before any run. Production was not touched.

Run Build Result
projects (new mode) origin/main 1.10.0 ❌ exit 1: session_start(Y) resumed X (f7c06c8b) and every call after it continued X. 6 checks failed, including "Y is a new session" and "Y never superseded".
projects this branch ✅ exit 0: 11/11 checks pass. X → 5ddc4fc4; Y → 6a15ba33 (new); X again → 5ddc4fc4 resumed; no project → 6a15ba33 resumed, naming blank-venue-y; X with force → new. Y is never superseded; X is superseded only by the same-project force.
flow this branch ✅ exit 0. Close persisted, relevance readable, thread counts match. Only GIT-73 and GIT-105 seen.
VENUE_ENV=<env outside repo> node tests/e2e/blank-supabase.mjs projects --label git86 --out <dir>

Unit tests: 1283/1283 pass (+9: 4 registry, 5 through the real session_start on the free tier). 8 of the 9 fail on origin/main. The 9th is a same-project resume guard, which passes on both.

Existing tests

None needed changing, and nothing was deleted. One test states the old invariant in its name: findResumableSessionOnDisk > adopts at most one session — never leaves two rows sharing hostname+pid. It still passes, because both of its sessions are in project default. The invariant is now "never two rows sharing hostname + pid + project". I left the name as it is; a rename is a one-line follow-up if wanted.

Checklist

  • npm run test:unit passes (1283)
  • npm run test:e2e: not run; the venue driver above is the e2e check for this change
  • New tests added
  • No schema change. Works on a v1.8.0 setup.sql store as-is.
  • Docs: no user-facing docs describe resume behaviour

Breaking changes

A process can now hold one open session per project instead of one in total. A caller that relied on session_start(project: Y) continuing its X session now gets a new Y session. That's the fix.

🤖 Generated with Claude Code

Claude and others added 2 commits September 21, 2026 20:02
… (GIT-86)

One desktop process serves every chat, so hostname+pid does not name a
session. session_start(Y) found the open X session by hostname+pid, replaced
the caller's project with X's (stderr only) and carried on in X.

- findSessionByHostPid and findResumableSessionOnDisk take the requested
  project and only return a session of that project.
- params.project set and no match: a new session. The registry slot is now
  hostname+pid+project, so registerSession no longer displaces X, X is not
  marked superseded, and pruneOrphanedDirs no longer sees X as unregistered.
- params.project absent: the most recent session is resumed, and the display
  names its project (project_from_resumed_session).
- force:true carries startedAt, surfaced scars, confirmations, observations
  and children forward only from a session of the same project. Confirmations
  were never carried before, and the free tier dropped the carried startedAt.
- session_close's registry fallback looks the session up by id.

+9 unit tests (4 registry, 5 through session_start); 8 fail on origin/main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…GIT-86)

session_start(X), session_start(Y), session_start(X), session_start({}),
session_start(X, force) through one server over stdio, as the desktop app
does. Checks the stored rows, close_compliance, the registry and the session
directories. Exits 1 on 1.10.0 (Y resumes X); exits 0 with the fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nTEG-dev nTEG-dev closed this pull request by merging all changes into main in f93c3a3 Sep 22, 2026
neoneye pushed a commit to agent-memory-atlas-archive/gitmem-dev--gitmem that referenced this pull request Sep 22, 2026
…itmem-dev#37)

GIT-86 (gitmem-dev#37) made session_close's registry fallback look the session up by
id. Four mocks of active-sessions.js exported only findSessionByHostPid —
session-close-validation (named in the stack plan), session-close-payload-path
(gitmem-dev#39, whose mock header was copied from it), session-close-supabase and
session-close-bridge. No test reached that fallback, so nothing failed; any
future one would have thrown on the missing export.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nTEG-dev
nTEG-dev deleted the bugfix/GIT-86-cross-project-resume 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