fix(session): don't hijack projectRoot binding to a nested git subdirectory#98
fix(session): don't hijack projectRoot binding to a nested git subdirectory#98Tom-Ma-Ming wants to merge 1 commit into
Conversation
When a stdio memorix serve starts already resolved to a parent repo (its
launch cwd), a later memorix_session_start({projectRoot:<parent>}) is a
same-project no-op. switchProject() signals that by returning false, which
the handler misread as 'not bound' and then scanned subdirectories, binding
the session to the first nested/vendored git repo (e.g. local/<sub>) instead
of the parent. This broke cross-agent scope: a stdio client (Claude Code) and
an HTTP-daemon client (workers) resolved the SAME directory to DIFFERENT
projects, so they did not actually share memory.
Fix: only fall back to the subdir scan when projectRoot itself is not a git
repo. When it is a real repo, a false from switchProject means same-project
no-op (success) — never scan subdirs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AVIDS2
left a comment
There was a problem hiding this comment.
Thanks for the fix and for digging into this edge case. I agree the underlying bug is real: projectRoot should not be hijacked to a nested git repo when the explicit root is already the currently bound parent repo.
I tested this patch on top of current main, and the intent looks correct, but I can't merge this revision as-is because it introduces regressions in the current HTTP/session lifecycle.
I reproduced with:
npm test -- tests/integration/session-nested-subdir-binding.test.ts tests/integration/serve-http.test.ts tests/integration/tool-profile.test.ts tests/project/detector-diagnostics.test.ts
On my side this branch currently fails 10 tests:
tests/integration/serve-http.test.ts(9 failures)tests/integration/tool-profile.test.ts(1 failure)
The failures are not about the new nested-subdir assertion itself; they are in the broader project binding / session-start path. The most important symptoms are:
memorix_session_start({ projectRoot })returnsisError: truein severalserve-httpscenarios where it should succeed.ObservationStore not initialized — call initObservationStore() firstis now surfacing inside the session-start path.- Several HTTP binding assertions no longer return the expected canonical project text after binding.
So I think we should keep the core idea of this PR, but rework it so it does not disturb the current switchProject / runtime initialization lifecycle.
I would be happy to review a rebased follow-up revision that preserves the nested-subdir protection while keeping the current serve-http and session_start test matrix green.
fix(session): don't hijack
projectRootbinding to a nested git subdirectoryProblem
When two clients share the same Memorix store but connect over different
transports, they can resolve the same directory to different projects,
so they silently stop sharing memory.
Concretely:
memorix serve(e.g. Claude Code) starts already resolved to aproject from its launch cwd.
memorix_session_start({ projectRoot: <that same repo> })then bindsthe session to a nested/vendored git repo under the project root
(e.g.
local/<subdir>) instead of the project itself.__unresolved__) binds the very sameprojectRootcorrectly to the parent repo.Result: the two agents think they're in different projects and never see each
other's memories.
Root cause
In the
memorix_session_starthandler,switchProject(projectRoot)returnsfalsein two distinct situations that were conflated:projectRootis a valid git repo, but it's the already-bound project— a same-project no-op. This is success.
projectRootis not a git repo — only here should the workspace root bescanned for a git project in a subdirectory.
The handler treated both
falsecases as "not bound" and fell through to thefindGitInSubdirsfallback, which bound the session to the first nested gitrepo it found. The same-project no-op (case 1) therefore got hijacked to a
subdirectory whenever the resolved project root happened to contain a vendored
git repo.
The HTTP path avoided this only incidentally: it starts unresolved, so the first
switchProjectperforms a real switch (returnstrue) and never reaches thefallback.
Fix
Only fall back to the subdirectory scan when
projectRootitself is not agit repo. When it is a real repo, a
falsefromswitchProjectmeans asame-project no-op (success) — never scan subdirs. The diagnostic resolution of
projectRootis done first to tell the two cases apart.Test
Adds
tests/integration/session-nested-subdir-binding.test.ts, which reproducesthe hijack: a server started resolved to a parent repo (with remote) that
contains a nested git repo (no remote).
memorix_session_start({ projectRoot: <parent> })must keep the parent bound and must not switch tolocal/<nested>.Red before the fix, green after. Existing
serve-http,tool-profile, andproject-detector suites continue to pass.