Skip to content

fix(session): don't hijack projectRoot binding to a nested git subdirectory#98

Open
Tom-Ma-Ming wants to merge 1 commit into
AVIDS2:mainfrom
Tom-Ma-Ming:fix/session-projectroot-nested-subdir
Open

fix(session): don't hijack projectRoot binding to a nested git subdirectory#98
Tom-Ma-Ming wants to merge 1 commit into
AVIDS2:mainfrom
Tom-Ma-Ming:fix/session-projectroot-nested-subdir

Conversation

@Tom-Ma-Ming

Copy link
Copy Markdown

fix(session): don't hijack projectRoot binding to a nested git subdirectory

Problem

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:

  • A stdio memorix serve (e.g. Claude Code) starts already resolved to a
    project from its launch cwd.
  • A later memorix_session_start({ projectRoot: <that same repo> }) then binds
    the session to a nested/vendored git repo under the project root
    (e.g. local/<subdir>) instead of the project itself.
  • An HTTP-daemon client (which starts __unresolved__) binds the very same
    projectRoot correctly 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_start handler, switchProject(projectRoot) returns
false in two distinct situations that were conflated:

  1. projectRoot is a valid git repo, but it's the already-bound project
    — a same-project no-op. This is success.
  2. projectRoot is not a git repo — only here should the workspace root be
    scanned for a git project in a subdirectory.

The handler treated both false cases as "not bound" and fell through to the
findGitInSubdirs fallback, which bound the session to the first nested git
repo 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
switchProject performs a real switch (returns true) and never reaches the
fallback.

Fix

Only fall back to the subdirectory scan when projectRoot itself is not a
git repo. When it is a real repo, a false from switchProject means a
same-project no-op (success) — never scan subdirs. The diagnostic resolution of
projectRoot is done first to tell the two cases apart.

Test

Adds tests/integration/session-nested-subdir-binding.test.ts, which reproduces
the 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 to local/<nested>.

Red before the fix, green after. Existing serve-http, tool-profile, and
project-detector suites continue to pass.

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 AVIDS2 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 }) returns isError: true in several serve-http scenarios where it should succeed.
  • ObservationStore not initialized — call initObservationStore() first is 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.

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.

3 participants