Problem
After #687 made basename(git rev-parse --show-toplevel) the default project key, two failure modes remain:
- Basename collisions —
~/work/utils (github.com/acme/utils) and ~/clients/foo/utils (github.com/foo/utils) both resolve to utils and silently share memory.
- Cross-machine fragmentation — a single agentmemory pod serving multiple MCP clients sees the same repo as different projects when contributor checkouts differ in basename. Concrete shape: central pod at
/srv/projects/foo, dev box at /home/<user>/src/foo, or simply foo-prod vs foo on a fork. AGENTMEMORY_PROJECT_NAME works around this but requires per-machine config for what is intrinsic to the repo.
Both fall away once the resolver consults remote.origin.url — the same identity src/hooks/post-commit.ts:65 already uses for commit attribution.
Proposal
Insert one step between the env override and the toplevel-basename fallback:
AGENTMEMORY_PROJECT_NAME
→ canonical(git config --get remote.origin.url)
→ basename(git rev-parse --show-toplevel) ← #687, unchanged
→ basename(cwd) ← unchanged
canonical produces host/owner/repo:
| Input |
Output |
https://github.com/acme/widgets.git |
github.com/acme/widgets |
git@github.com:Acme/Widgets.git |
github.com/acme/widgets |
ssh://git@host.example:2222/acme/widgets.git |
host.example/acme/widgets |
https://user:token@github.com/acme/widgets |
github.com/acme/widgets |
Lowercased end-to-end (host providers treat owner/repo case-insensitively; preserving path case would re-fragment same-repo clones). Trailing .git stripped, credentials stripped. Unparseable remotes fall through to basename.
Why this and not #530
#530 proposed canonicalization plus multi-key matching across canonical/cwd/worktree-top/main-root/aliases. This issue keeps the single-key partitioning #687 just shipped — the change is where the key comes from, not how it's matched. No migration of existing data; deployments that already set AGENTMEMORY_PROJECT_NAME see no behavior change.
Non-goals
- Monorepo sub-package partitioning. Every package under
github.com/acme/monorepo resolves to the same key by design — partitioning within a monorepo is an orthogonal concern (the project axis isn't the right one for it). Operators who need per-package memory continue to set AGENTMEMORY_PROJECT_NAME per-package.
- Multi-remote awareness (forks).
origin is sufficient for the two failure modes above. AGENTMEMORY_PROJECT_NAME remains the override for fork workflows that need to point at upstream.
- SSH config aliases,
insteadOf rewrites. Resolver consumes the raw URL git emits; if a contributor configures an alias differently across machines, that's the same class of problem as setting different env vars and is solved by the same escape hatch.
Performance
One git config --get remote.origin.url invocation, capped at 500ms (same budget the rest of _project.ts already uses). Same shape as #530's tightened lookup discussed by @rockarxiv on that PR.
Patch shape
~60 LOC in src/hooks/_project.ts, 12 new unit tests, full suite (1303 tests) green. Happy to discuss the approach before opening the PR if there's a different shape you'd prefer; otherwise the PR will follow.
Problem
After #687 made
basename(git rev-parse --show-toplevel)the default project key, two failure modes remain:~/work/utils(github.com/acme/utils) and~/clients/foo/utils(github.com/foo/utils) both resolve toutilsand silently share memory./srv/projects/foo, dev box at/home/<user>/src/foo, or simplyfoo-prodvsfooon a fork.AGENTMEMORY_PROJECT_NAMEworks around this but requires per-machine config for what is intrinsic to the repo.Both fall away once the resolver consults
remote.origin.url— the same identitysrc/hooks/post-commit.ts:65already uses for commit attribution.Proposal
Insert one step between the env override and the toplevel-basename fallback:
canonicalproduceshost/owner/repo:https://github.com/acme/widgets.gitgithub.com/acme/widgetsgit@github.com:Acme/Widgets.gitgithub.com/acme/widgetsssh://git@host.example:2222/acme/widgets.githost.example/acme/widgetshttps://user:token@github.com/acme/widgetsgithub.com/acme/widgetsLowercased end-to-end (host providers treat owner/repo case-insensitively; preserving path case would re-fragment same-repo clones). Trailing
.gitstripped, credentials stripped. Unparseable remotes fall through to basename.Why this and not #530
#530 proposed canonicalization plus multi-key matching across canonical/cwd/worktree-top/main-root/aliases. This issue keeps the single-key partitioning #687 just shipped — the change is where the key comes from, not how it's matched. No migration of existing data; deployments that already set
AGENTMEMORY_PROJECT_NAMEsee no behavior change.Non-goals
github.com/acme/monoreporesolves to the same key by design — partitioning within a monorepo is an orthogonal concern (theprojectaxis isn't the right one for it). Operators who need per-package memory continue to setAGENTMEMORY_PROJECT_NAMEper-package.originis sufficient for the two failure modes above.AGENTMEMORY_PROJECT_NAMEremains the override for fork workflows that need to point atupstream.insteadOfrewrites. Resolver consumes the raw URL git emits; if a contributor configures an alias differently across machines, that's the same class of problem as setting different env vars and is solved by the same escape hatch.Performance
One
git config --get remote.origin.urlinvocation, capped at 500ms (same budget the rest of_project.tsalready uses). Same shape as #530's tightened lookup discussed by @rockarxiv on that PR.Patch shape
~60 LOC in
src/hooks/_project.ts, 12 new unit tests, full suite (1303 tests) green. Happy to discuss the approach before opening the PR if there's a different shape you'd prefer; otherwise the PR will follow.