Prompt hooks take 40-55s on large worktrees: go-git worktree.Status() walks gitignored directories (git status: 0.02s; patched locally to 193ms)
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 5.1k
- Forks
- 475
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 178
Description
Summary
On a large worktree, entire hooks claude-code user-prompt-submit takes 39–55 seconds per prompt, nearly all of it in capture_pre_prompt_state and init_session. The same worktree's git status --porcelain completes in 0.02s. Root cause (confirmed by patching, see bottom): three hot paths still use go-git's worktree.Status(), which stats and hashes every file on disk — including gitignored directories — while several sibling call sites were already converted to the native git CLI for exactly this reason (e.g. the comments in checkpoint/ephemeral.go and strategy/manual_commit_hooks.go:getStagedFiles).
Environment
- Entire CLI 0.7.8 (also reproduced on 0.7.7); patched build based on
main@ 4398a44 - macOS 15 (Darwin 25.5.0), arm64
- go1.26.4 darwin/arm64
- Agent: Claude Code,
manual-commitstrategy - Swift monorepo: 61GB on disk, ~686k files — ~34GB of that in nine gitignored SwiftPM
.build/directories.git status --porcelainreturns 0–21 lines.
What we did / measured
Ran the hook directly with a captured payload, then entire doctor trace with ENTIRE_LOG_LEVEL=DEBUG:
user-prompt-submit 45954ms
STEP DURATION
capture_pre_prompt_state 24397ms
init_session 21540ms
Debug log — the 24s sits between hook start and the state-capture line, which found only 21 untracked files:
16:58:26.465 DEBUG hook invoked hook=user-prompt-submit
16:58:50.873 DEBUG captured state before prompt untracked_files=21 transcript_offset=0
16:59:12.420 DEBUG perf op=user-prompt-submit duration_ms=45954 steps.capture_pre_prompt_state_ms=24397 steps.init_session_ms=21540
Profile is I/O bound (~3s user / ~24s sys). Reproduces on every prompt. Comparison on the identical worktree: git status --porcelain = 0.02s real.
Turn-end shows the same signature: every turn-end in the log is followed 20–50s later by committed changes to shadow branch (that path hits DetectFileChanges, see below).
Ruled out
- Metadata branch divergence (repaired; doctor green — no timing change). Side note: doctor's cherry-pick reconciliation aborts on chains >1000 commits ("commit chain exceeded 1000 commits without reaching root"), which may deserve its own issue.
.entire/tmpgrowth (162MB → 8KB — no change)- Version 0.7.7 → 0.7.8 (identical timings)
- Removing 7.2GB of the ignored build dirs shaved ~5s — time scales with ignored-dir bytes.
Root cause + validated patch
The three remaining go-git worktree.Status() call sites on the hook path:
cmd/entire/cli/state.go—getUntrackedFilesForState(→capture_pre_prompt_state, 24.4s)cmd/entire/cli/strategy/manual_commit_hooks.go—calculatePromptAttributionAtStart(→init_session, 21.5s)cmd/entire/cli/state.go—DetectFileChanges(→ the 20–50s turn-end shadow-branch commit delay)
We patched locally: one exported strategy.GitCLIStatus() helper that runs git status --porcelain=v1 -z -uall --no-renames and parses it into a go-git-compatible git.Status map (porcelain v1 status letters are byte-identical to go-git's StatusCode values), then swapped it into all three sites, plus a one-line test-scaffold fix (.git/refs/ so the native CLI recognizes the hand-rolled test repos).
Result on the same repo, same trace tooling:
user-prompt-submit 193ms (was 45954ms — ~238x)
capture_pre_prompt_state 102ms (was 24397ms)
init_session 79ms (was 21540ms)
go test ./cmd/entire/cli ./cmd/entire/cli/strategy: 989 passed; the single failure (TestRunUninstall_Force_NothingInstalled) is pre-existing and environment-dependent (fails identically on pristine main when the CLI is installed on the machine).
Per CONTRIBUTING.md we're opening this issue first: happy to submit the PR (patch + tests, mise lint/test, checkpoint trailers) on maintainer approval.
Side note (macOS packaging)
The Homebrew cask binary for 0.7.8 is ad-hoc signed / not notarized and hangs in _dyld_start under Gatekeeper on macOS 15, even after removing com.apple.quarantine. The install-script binary runs fine. Probably warrants a separate issue.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with getUntrackedFilesForState and DetectFileChanges in cmd/entire/cli/state.go, then inspect calculatePromptAttributionAtStart in cmd/entire/cli/strategy/manual_commit_hooks.go and the existing Git CLI usage in sibling strategy code. Run go test ./cmd/entire/cli ./cmd/entire/cli/strategy, including the hand-rolled repository fixtures. Done means hook and turn-end paths avoid scanning ignored files while preserving status results and the existing tests pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, go
- Domain
- cli, devtools, performance
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100