Found while running agent fleets in this repo. Claude Code creates isolated worktrees at .claude/worktrees/agent-<id>/, each a full checkout of the repository. codemap walks straight into them and counts every file as part of the project.
Impact, measured
With 18 agent worktrees present, then removing them:
$ codemap . # 18 worktrees present
│ Files: 3943 | Size: 5.7MB
$ git worktree remove ... # removed 18 finished worktrees
$ codemap .
│ Files: 718 | Size: ...
5.5x inflation. This repo has roughly 150 source files.
Everything derived from the walk is affected:
codemap . file count and the session-start hook that every agent sees first
codemap context — reported file_count: 3223, and the working_set filled with worktree paths
codemap handoff — the entire "Delta (Recent Work)" section was 18 .claude/worktrees/agent-*/ (untracked) lines, pushing real changed files out of the artifact
- hub detection and importer counts, since the same file is indexed once per worktree copy
The handoff case is the worst: the artifact whose whole job is telling the next agent what was recently worked on was reporting worktree directories instead.
Root cause
scanner.IgnoredDirs (scanner/walker.go:113) has ~30 entries — .git, node_modules, vendor, target, .next, .venv, and so on — but nothing covering agent worktrees. .git only skips the directory named .git; a linked worktree's .git is a file containing a gitdir: pointer, and the worktree directory itself has an ordinary name.
.claude/worktrees/ is also not gitignored, so the gitignore path doesn't catch it either:
$ git check-ignore -v .claude/worktrees/
(no match)
The interesting part: the detection already exists, and is ast-grep-only
findNestedGitRepos (scanner/astgrep.go:190) already finds nested repositories and excludes them from the ast-grep scan via --globs !repo/** (astgrep.go:267). It is the only consumer — the file walker never calls it.
So --deps partially avoids the problem while codemap ., context, and handoff do not. That inconsistency is worth fixing on its own.
It would also miss worktrees even if the walker did call it, for three independent reasons:
for _, e := range entries {
if !e.IsDir() || strings.HasPrefix(e.Name(), ".") { continue } // (1) skips .claude entirely
gitPath := filepath.Join(root, e.Name(), ".git")
if info, err := os.Stat(gitPath); err == nil && info.IsDir() { // (3) worktree .git is a FILE
repos = append(repos, e.Name())
}
}
- Dotted directories are skipped, so
.claude/ is never examined.
- Only top-level entries are read (one
os.ReadDir, no recursion), so .claude/worktrees/agent-X is two levels deeper than it looks.
- It requires
.git to be a directory. A linked worktree's .git is a regular file holding gitdir: /path/to/.git/worktrees/<name>, so info.IsDir() is false.
Confirmed a nested worktree is genuinely a separate work tree:
$ git -C .claude/worktrees/agent-<id> rev-parse --show-toplevel
/Users/…/codemap/.claude/worktrees/agent-<id>
Suggested fix
Teach the walker to skip any directory that is a distinct git work tree, and share one implementation with ast-grep instead of keeping the logic in two places:
- Treat a
.git entry as a boundary marker whether it is a directory or a regular file (the worktree/submodule form).
- Don't skip dotted directories when looking for boundaries —
.claude/worktrees/ is exactly where agent harnesses put them.
- Detect at any depth, not just top level.
A cheap interim mitigation is adding .claude to IgnoredDirs, but that only covers one harness's convention; the boundary check is the general fix and also handles vendored repos and submodules.
Worth noting this will get more common, not less — agent harnesses creating in-repo worktrees is becoming the default, and codemap is a tool agents run.
Found while running agent fleets in this repo. Claude Code creates isolated worktrees at
.claude/worktrees/agent-<id>/, each a full checkout of the repository. codemap walks straight into them and counts every file as part of the project.Impact, measured
With 18 agent worktrees present, then removing them:
5.5x inflation. This repo has roughly 150 source files.
Everything derived from the walk is affected:
codemap .file count and the session-start hook that every agent sees firstcodemap context— reportedfile_count: 3223, and theworking_setfilled with worktree pathscodemap handoff— the entire "Delta (Recent Work)" section was 18.claude/worktrees/agent-*/ (untracked)lines, pushing real changed files out of the artifactThe handoff case is the worst: the artifact whose whole job is telling the next agent what was recently worked on was reporting worktree directories instead.
Root cause
scanner.IgnoredDirs(scanner/walker.go:113) has ~30 entries —.git,node_modules,vendor,target,.next,.venv, and so on — but nothing covering agent worktrees..gitonly skips the directory named.git; a linked worktree's.gitis a file containing agitdir:pointer, and the worktree directory itself has an ordinary name..claude/worktrees/is also not gitignored, so the gitignore path doesn't catch it either:The interesting part: the detection already exists, and is ast-grep-only
findNestedGitRepos(scanner/astgrep.go:190) already finds nested repositories and excludes them from the ast-grep scan via--globs !repo/**(astgrep.go:267). It is the only consumer — the file walker never calls it.So
--depspartially avoids the problem whilecodemap .,context, andhandoffdo not. That inconsistency is worth fixing on its own.It would also miss worktrees even if the walker did call it, for three independent reasons:
.claude/is never examined.os.ReadDir, no recursion), so.claude/worktrees/agent-Xis two levels deeper than it looks..gitto be a directory. A linked worktree's.gitis a regular file holdinggitdir: /path/to/.git/worktrees/<name>, soinfo.IsDir()is false.Confirmed a nested worktree is genuinely a separate work tree:
Suggested fix
Teach the walker to skip any directory that is a distinct git work tree, and share one implementation with ast-grep instead of keeping the logic in two places:
.gitentry as a boundary marker whether it is a directory or a regular file (the worktree/submodule form)..claude/worktrees/is exactly where agent harnesses put them.A cheap interim mitigation is adding
.claudetoIgnoredDirs, but that only covers one harness's convention; the boundary check is the general fix and also handles vendored repos and submodules.Worth noting this will get more common, not less — agent harnesses creating in-repo worktrees is becoming the default, and codemap is a tool agents run.