entireio / entireio/cli

Prompt hooks take 40-55s on large worktrees: go-git worktree.Status() walks gitignored directories (git status: 0.02s; patched locally to 193ms)

Open
#1,642 0 comments 1 reaction 0 assignees View on GitHub

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-commit strategy
  • Swift monorepo: 61GB on disk, ~686k files — ~34GB of that in nine gitignored SwiftPM .build/ directories. git status --porcelain returns 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/tmp growth (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:

  1. cmd/entire/cli/state.gogetUntrackedFilesForState (→ capture_pre_prompt_state, 24.4s)
  2. cmd/entire/cli/strategy/manual_commit_hooks.gocalculatePromptAttributionAtStart (→ init_session, 21.5s)
  3. cmd/entire/cli/state.goDetectFileChanges (→ 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.