Repository navigation
fix: hooks follow the agent's cwd — subdirectories and git worktrees - #5
Merged
Merged
Conversation
`find_project_dir` took the hook's `cwd` as the project root. Claude Code
documents that `cwd` follows the agent: after a `cd src/` it is the
subdirectory, and `claude` launched from a monorepo package starts there too.
The store and `.lore.json` were then looked up in the wrong directory, so:
cwd = <repo> recall surfaces learnings/ci/cache.md
cwd = <repo>/src/deep recall prints nothing -- even with
CLAUDE_PROJECT_DIR set to <repo>
Everything built on it went quiet the same way: edit-time recall, the
capture nudge (which also stopped honoring `captureNudge: "off"`, since the
config was never found), the post-compaction reminder, and /lore:lint,
/lore:scan and /lore:mine run from a subdirectory.
Search upward from the start directory for the nearest `.lore.json` or
`learnings/` instead. `CLAUDE_PROJECT_DIR` is not used as the answer on
purpose: it stays on the original checkout when the agent enters a git
worktree, and reading that checkout's store would break path matching for
files edited in the worktree.
The walk is bounded: it never leaves the enclosing git repository (a stray
`~/learnings` must not answer for an unrelated project), and outside any
repository only the start directory is checked, which is the old behavior.
Tests: the reproduction above is a hook-level test, plus unit tests for the
marker lookup, the `.lore.json` marker, the repository bound, the no-repo
case and the $CLAUDE_PROJECT_DIR start. The four that exercise the bug fail
without the change; the two bound tests pin behavior that must not regress.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DMXx6BJswH6xiwdqrwQnEf
Both kept their state under `<project>/.git` and bailed unless that was a
directory. In a linked worktree -- `git worktree add`, which is how parallel
agents usually run -- `.git` is a one-line FILE (`gitdir: <path>`), so:
edit 1 in the worktree learning injected
edit 2, same session injected again (and on every edit after)
.git/lore-recall.log never written: /lore:stats saw nothing
Resolve the pointer instead (`git_dirs` in _common): the worktree's private
git dir holds the dedupe state, so agents in separate worktrees never reset
each other's; its `commondir` names the repository's shared `.git`, which
holds the recall log, so every worktree feeds the same /lore:stats numbers and
an ephemeral worktree takes none of them with it when it is removed. A
submodule's relative `gitdir:` with no `commondir` resolves to its own dir.
It reads the pointer from disk rather than running `git rev-parse`, because
the hooks run on every prompt and every edit.
Tests: a real `git worktree add` drives the edit-time hook twice (dedupe) and
checks the shared log; the linter reads that log from a worktree; unit tests
cover plain, worktree, submodule and malformed pointers. The three behavior
tests fail without the change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DMXx6BJswH6xiwdqrwQnEf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Claude Code documents that the hook input's
cwdfollows the agent: it is the new directory after acd, and the worktree root once the agent enters a worktree. Two places assumed hooks always run at the project root:find_project_dirtookcwdliterally. Aftercd src/, or withclaudelaunched from a monorepo package, recall, edit-time recall, the capture nudge (includingcaptureNudge: "off"), the post-compaction reminder and/lore:lint|scan|mineall looked for the store in the wrong directory and went silent. It now searches upward for the nearest.lore.jsonorlearnings/, bounded by the enclosing git repository. Outside a repository only the start directory is checked, as before..gitto be a directory. In a linked worktree.gitis agitdir:file, so the same learning was re-injected on every edit and nothing was logged.git_dirs()resolves the pointer by reading it from disk (no subprocess in the hooks). Dedupe state is kept per worktree; the recall log goes in the shared.git, so/lore:statssees every worktree.Test plan
git worktree add. They fail onmainand pass here.python -m unittest discover -s tests: 182 passed, 1 skipped.fix/skill-script-pathsandfix/linter-blind-spotsin any order; the combined suite is green.🤖 Generated with Claude Code
https://claude.ai/code/session_01DMXx6BJswH6xiwdqrwQnEf
Generated by Claude Code