Skip to content

perf: optimize stop hook performance#141

Merged
gtrrz-victor merged 15 commits intomainfrom
alex/investigate-stop-slowness
Feb 5, 2026
Merged

perf: optimize stop hook performance#141
gtrrz-victor merged 15 commits intomainfrom
alex/investigate-stop-slowness

Conversation

@khaong
Copy link
Contributor

@khaong khaong commented Feb 4, 2026

Summary

  • Consolidate redundant git status calls into single ComputeFileChanges function
  • Eliminate double transcript parsing in CalculateTotalTokenUsage

Performance Improvement

Optimization Savings
Git status consolidation ~40-80ms
Transcript parsing ~0.3-0.6ms
Total ~40-80ms

Measured in repos with 400+ files. The main win comes from calling worktree.Status() once instead of twice.

Test plan

  • All unit tests pass
  • All integration tests pass
  • Linting clean

🤖 Generated with Claude Code


Note

Medium Risk
Touches checkpoint tree construction and file-change detection around session start, which can affect what gets committed (especially deletions/renames/ignored files); behavior is covered by added tests but relies on invoking the git CLI and parsing porcelain output.

Overview
Reduces stop-hook overhead by consolidating new and deleted file detection into a single ComputeFileChanges call, updating Claude Code and Gemini hook handlers (and debug auto-commit) to use it while deprecating the old split helpers.

Optimizes temporary checkpoint creation for the first checkpoint by replacing a full working-directory walk with a git status --porcelain -z -uall based collector that respects repo + global gitignore, and by merging user pre-existing deletions into the checkpoint tree; adds extensive tests covering first-checkpoint capture of modified tracked files, untracked files, gitignored exclusions, deletions, renames, and filenames with spaces.

Avoids double transcript parsing in Claude Code token accounting by parsing once and reusing the parsed transcript for both token usage and spawned subagent detection (including handling empty transcript paths).

Written by Cursor Bugbot for commit 941f759. This will update automatically on new commits. Configure here.

@khaong khaong requested a review from a team as a code owner February 4, 2026 00:32
Copilot AI review requested due to automatic review settings February 4, 2026 00:32
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes stop-hook performance by consolidating git status queries and avoiding redundant transcript parsing when calculating Claude Code token usage.

Changes:

  • Introduces ComputeFileChanges to compute new + deleted files from a single worktree.Status() call and updates hook/debug call sites to use it.
  • Deprecates ComputeNewFiles / ComputeDeletedFiles in favor of the consolidated implementation.
  • Updates CalculateTotalTokenUsage to parse the transcript once and reuse the parsed result for both token usage and spawned-agent detection.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
cmd/entire/cli/state.go Adds ComputeFileChanges and marks older file-change helpers as deprecated
cmd/entire/cli/state_test.go Adds a minimal test stub for ComputeFileChanges
cmd/entire/cli/hooks_geminicli_handlers.go Switches Gemini hook to consolidated file-change computation
cmd/entire/cli/hooks_claudecode_handlers.go Switches Claude Code hook to consolidated file-change computation
cmd/entire/cli/debug.go Updates debug output to use consolidated file-change computation
cmd/entire/cli/agent/claudecode/transcript.go Removes double transcript parsing in CalculateTotalTokenUsage

@khaong khaong marked this pull request as draft February 4, 2026 00:38
@khaong khaong marked this pull request as ready for review February 4, 2026 00:49
@khaong khaong requested a review from Copilot February 4, 2026 00:50
@khaong khaong marked this pull request as draft February 4, 2026 00:50
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

@khaong khaong requested a review from Copilot February 4, 2026 01:21
@khaong khaong marked this pull request as ready for review February 4, 2026 01:21
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

@khaong khaong enabled auto-merge February 4, 2026 01:52
@gtrrz-victor gtrrz-victor force-pushed the alex/investigate-stop-slowness branch from ff006a0 to 3070690 Compare February 5, 2026 04:41
khaong and others added 12 commits February 5, 2026 15:45
Combine ComputeNewFiles and ComputeDeletedFiles into single
ComputeFileChanges function that calls worktree.Status() once
instead of twice. This reduces stop hook latency by ~40-80ms
in repos with 400+ files.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Entire-Checkpoint: 6beaf3327136
Refactor CalculateTotalTokenUsage to parse the transcript once
and extract both token usage and spawned agent IDs from the same
parsed data. Previously it called parseTranscriptFromLine twice.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Entire-Checkpoint: 6beaf3327136
Address PR review feedback from Copilot and Cursor Bugbot:
- ComputeFileChanges now always computes deleted files regardless
  of preState (deleted files don't depend on pre-prompt state)
- Only newFiles computation requires preState to compare against
- Update test name and comments to accurately reflect behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Address PR review feedback:
- debug.go: Call ComputeFileChanges(nil) even when no active session
  to still detect deleted files (they don't depend on preState)
- state_test.go: Replace weak stub test with comprehensive tests that
  create isolated git repos and verify both new and deleted file
  detection behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Entire-Checkpoint: f9e69d99c484
ComputeFileChanges uses strategy.OpenRepository() which calls
GetWorktreePath() (git rev-parse --show-toplevel), not paths.RepoRoot().
The cache clearing was unnecessary and the comments were misleading.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Entire-Checkpoint: a09495d15235
Combine ComputeNewFiles and ComputeDeletedFiles into single
ComputeFileChanges function that calls worktree.Status() once
instead of twice. This reduces stop hook latency by ~40-80ms
in repos with 400+ files.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Entire-Checkpoint: 6beaf3327136
Address PR review feedback from Copilot and Cursor Bugbot:
- ComputeFileChanges now always computes deleted files regardless
  of preState (deleted files don't depend on pre-prompt state)
- Only newFiles computation requires preState to compare against
- Update test name and comments to accurately reflect behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Address PR review feedback:
- debug.go: Call ComputeFileChanges(nil) even when no active session
  to still detect deleted files (they don't depend on preState)
- state_test.go: Replace weak stub test with comprehensive tests that
  create isolated git repos and verify both new and deleted file
  detection behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Entire-Checkpoint: f9e69d99c484
ComputeFileChanges uses strategy.OpenRepository() which calls
GetWorktreePath() (git rev-parse --show-toplevel), not paths.RepoRoot().
The cache clearing was unnecessary and the comments were misleading.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Entire-Checkpoint: a09495d15235
…kpoint

Replace collectWorkingDirectoryFiles() with collectChangedFiles() to fix
slow shadow branch creation when node_modules/ or .worktrees/ exist.

The old implementation walked the entire filesystem, only skipping .git/
and .entire/ directories. This was extremely slow in repos with large
ignored directories like node_modules/ (50k+ files).

The new implementation uses worktree.Status() which:
- Respects .gitignore (excludes node_modules/, build/, etc.)
- Only returns changed files (modified tracked + untracked non-ignored)
- Is much faster: O(changed files) vs O(all files)

The base tree from HEAD already contains all unchanged tracked files,
so we only need to capture files that have changed.

Added tests to verify:
- Modified tracked files are captured (user's uncommitted work)
- Untracked non-ignored files are captured
- Gitignored files are excluded
- Both user and agent changes are captured together

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Entire-Checkpoint: 4dee03b57bf0
Address PR review feedback: replace slices.Contains (O(n)) with map
lookup (O(1)) for deduplication, removing the slices import.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Entire-Checkpoint: 0faddab5d923
- Clarify comments about go-git's worktree.Status() respecting repo
  .gitignore but not global gitignore (core.excludesfile)
- Make test assertion more specific: check for ErrFileNotFound/ErrEntryNotFound
  instead of just non-nil error

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Entire-Checkpoint: 2bd1d37452a2
khaong and others added 3 commits February 5, 2026 15:47
Switch from go-git's worktree.Status() to native git CLI commands for
collecting changed files during checkpoint creation. This ensures full
gitignore compatibility including global gitignore (core.excludesfile).

Key changes:
- Use `git status --porcelain -uall` to get all changed files
- Set cmd.Dir to repo root for correct execution in test environments
- Rename inner `worktree` variable to `wtStatus` to avoid shadowing

This aligns with the safety approach used in PR #129 for other git
operations where go-git has known issues.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Entire-Checkpoint: b4b052c197de
Address PR feedback:
- Switch to `git status --porcelain -z` for NUL-separated output
  (handles quoted filenames with spaces/special chars correctly)
- Pass context from caller for cancellation/timeout support
- Handle renamed files (R) and copied files (C) with proper path parsing
- Capture user's pre-existing file deletions (D) in first checkpoint
- Handle type changes (T) and unmerged files (U) for completeness
- Update stale comment that mentioned worktree.Status()

Add tests for deleted files, renamed files, and filenames with spaces.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move R/C handling before infrastructure path check to ensure old name
  entry is always skipped, even if new name is an infrastructure path
- Use git mv in rename test to ensure R status (not D+A)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Entire-Checkpoint: e26858f99834
@gtrrz-victor gtrrz-victor force-pushed the alex/investigate-stop-slowness branch from 3070690 to 941f759 Compare February 5, 2026 04:47
@gtrrz-victor gtrrz-victor disabled auto-merge February 5, 2026 04:53
@gtrrz-victor gtrrz-victor merged commit 9b38d84 into main Feb 5, 2026
4 checks passed
@gtrrz-victor gtrrz-victor deleted the alex/investigate-stop-slowness branch February 5, 2026 05:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants