You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
install_hooks writes the auto-reindex hook into the main repository's .git/hooks/. Git worktrees share that directory through the common dir, so every git worktree add, commit and merge inside any worktree fires the hook and detaches another unbounded cce index.
For anyone running AI agents in git worktrees this scales with agent parallelism. On my machine it reached 36 concurrent indexers at ~1.85 GB RSS each (~66 GB on a 96 GB box), load average 426, and took down an unrelated production service on the same host — its origin stopped answering and Cloudflare served a connection error to users. The web app itself was healthy; the machine was simply out of memory.
This is distinct from #139 (68 cce serve processes). Same class of failure, different process and different trigger — and, as noted below, the 0.4.26 resource governor does not cover this path.
.git/hooks is shared with every worktree. In a worktree .git is a file pointing at <main>/.git/worktrees/<name>/, and hook lookup resolves to the common dir — so a hook installed once in the main checkout runs for all N worktrees. Improve git worktree support: hook installer + storage path collision #48 observed the inverse (hooks don't install from inside a worktree); the consequence that main-repo hooks fire for every worktree seems not to have been considered.
The spawn is unbounded.& detaches it, so the process is orphaned to PID 1, git returns instantly, and nothing serialises, nices, or caps the result. There is no lock and no awareness of sibling indexers.
Creating a worktree fires post-checkout; the agent's first commit fires post-commit; a merge fires post-merge. I observed three separate indexers against a single worktree from one agent's activity.
Observed
35–36 concurrent cce index, all PPID 1, oldest ~3h49m
~1.85 GB RSS each, ≈66 GB of 96 GB
Load average 426 (15-min average sustained >200 for hours)
8 concurrent agents, each cutting its own worktree under /private/tmp/w-*
Recovery required killing all of them by hand; memory returned to 83% free immediately
Why the 0.4.26 resource governor (#142) does not cover this
I upgraded to 0.4.26 expecting it to help, and on reading it, it does not address this path:
Mechanism
Why it misses
Per-project index lock (fcntl.flock)
Keyed per project, and every worktree resolves to a distinct project slug — so the lock never engages between worktrees. It dedupes same-project duplicates only.
Linux PSI memory-pressure deferral
Reads /proc/pressure/memory; explicitly a no-op off Linux. Nothing on macOS, which is where the agent-worktree workflow is common.
Idle auto-shutdown, ORT thread caps
Both apply to cce serve. This failure is standalone cce index invoked from a git hook.
Detect worktrees in install_hooks / the hook itself and skip, or coalesce to the main checkout. git rev-parse --git-common-dir distinguishes a worktree from a main checkout in one call.
nice the hook-spawned process. It's background maintenance; it should never compete with the user's foreground work.
Skip ephemeral worktrees. Agent harnesses create throwaway trees (/private/tmp/w-*, <repo>/.claude/worktrees/*) that are deleted minutes later — indexing them is pure cost, since the index dies with the tree.
Repoint the hooks at a wrapper that holds an fcntl semaphore (macOS has no flock(1)), caps concurrency machine-wide, dedupes per directory, runs at nice 10, and skips ephemeral worktree paths. After that change the same workload produces at most 2 concurrent indexers and the machine stays responsive.
Worth noting for anyone applying a local fix: cce init rewrites the hook files, so re-running it silently discards the workaround.
Summary
install_hookswrites the auto-reindex hook into the main repository's.git/hooks/. Git worktrees share that directory through the common dir, so everygit worktree add, commit and merge inside any worktree fires the hook and detaches another unboundedcce index.For anyone running AI agents in git worktrees this scales with agent parallelism. On my machine it reached 36 concurrent indexers at ~1.85 GB RSS each (~66 GB on a 96 GB box), load average 426, and took down an unrelated production service on the same host — its origin stopped answering and Cloudflare served a connection error to users. The web app itself was healthy; the machine was simply out of memory.
This is distinct from #139 (68
cce serveprocesses). Same class of failure, different process and different trigger — and, as noted below, the 0.4.26 resource governor does not cover this path.Root cause
src/context_engine/indexer/git_hooks.py:Two properties combine badly:
.git/hooksis shared with every worktree. In a worktree.gitis a file pointing at<main>/.git/worktrees/<name>/, and hook lookup resolves to the common dir — so a hook installed once in the main checkout runs for all N worktrees. Improve git worktree support: hook installer + storage path collision #48 observed the inverse (hooks don't install from inside a worktree); the consequence that main-repo hooks fire for every worktree seems not to have been considered.&detaches it, so the process is orphaned to PID 1, git returns instantly, and nothing serialises, nices, or caps the result. There is no lock and no awareness of sibling indexers.Creating a worktree fires
post-checkout; the agent's first commit firespost-commit; a merge firespost-merge. I observed three separate indexers against a single worktree from one agent's activity.Observed
cce index, all PPID 1, oldest ~3h49m/private/tmp/w-*Why the 0.4.26 resource governor (#142) does not cover this
I upgraded to 0.4.26 expecting it to help, and on reading it, it does not address this path:
fcntl.flock)/proc/pressure/memory; explicitly a no-op off Linux. Nothing on macOS, which is where the agent-worktree workflow is common.cce serve. This failure is standalonecce indexinvoked from a git hook.Suggested fixes
Any one of these would have prevented it:
install_hooks/ the hook itself and skip, or coalesce to the main checkout.git rev-parse --git-common-dirdistinguishes a worktree from a main checkout in one call.nicethe hook-spawned process. It's background maintenance; it should never compete with the user's foreground work./private/tmp/w-*,<repo>/.claude/worktrees/*) that are deleted minutes later — indexing them is pure cost, since the index dies with the tree.Related: #48 (hooks and worktrees), #52 (slug collision), #139/#142 (multi-instance resource exhaustion).
Workaround for others hitting this
Repoint the hooks at a wrapper that holds an
fcntlsemaphore (macOS has noflock(1)), caps concurrency machine-wide, dedupes per directory, runs atnice 10, and skips ephemeral worktree paths. After that change the same workload produces at most 2 concurrent indexers and the machine stays responsive.Worth noting for anyone applying a local fix:
cce initrewrites the hook files, so re-running it silently discards the workaround.