fix: make checkpoint status reflect hook health - #2343
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 598bc4f. Configure here.
| if repairErr != nil && health.State == strategy.GitHookIntegrationCurrent { | ||
| health.State = strategy.GitHookIntegrationError | ||
| health.ReasonCode = "hook_repair_failed" | ||
| } |
There was a problem hiding this comment.
Setup errors reported as hook failures
Medium Severity
Turn-start hook repair treats any EnsureSetup failure as a broken Git-hook integration, even when hooks already inspect as current. EnsureSetup can fail earlier on gitignore, repo open, Vercel settings, or the primary metadata ref, and only then attempts hook install. That override surfaces a false hook-health warning and records a hook_repair_failed fingerprint that suppresses later warnings for the same classification.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 598bc4f. Configure here.
| name := manager.ConfigPath | ||
| if strings.HasPrefix(name, ".") && name != ".lefthook.yml" && name != ".lefthook.yaml" { | ||
| return fmt.Errorf("safe Entire Lefthook integration inspection does not support config location %s", name) | ||
| } |
There was a problem hiding this comment.
Config-dir Lefthook layouts cannot integrate
Medium Severity
Detection enumerates Lefthook’s standard .config/lefthook.{yml,yaml} locations, but validateLefthookMainConfig rejects any dotted path other than .lefthook.yml / .lefthook.yaml. A repo whose only main config lives under .config/ is selected as Lefthook, then fails validation, so EnsureGitHookIntegration and entire enable abort instead of installing a working integration.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 598bc4f. Configure here.
Entire-Checkpoint: 01M249K6A6PDMRNWJNP2A7CSE4
There was a problem hiding this comment.
🟡 Changes recommended
Five moderate review findings remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR makes checkpoint capture and sync health durable and visible across native Git hooks and Lefthook.
Changes:
- Adds transactional, ownership-aware hook installation and repair.
- Reports hook health and checkpoint-sync readiness across lifecycle, status, and doctor.
- Adds warning deduplication, read-only session reporting, documentation, and test coverage.
File summaries
| File | Description |
|---|---|
redact/betterleaks_env_test.go |
Tests portable module replacement handling. |
e2e/tests/lefthook_durable_test.go |
Covers durable Lefthook checkpoint capture. |
docs/architecture/sessions-and-checkpoints.md |
Documents hook health and checkpoint status behavior. |
cmd/entire/cli/strategy/session_state.go |
Persists hook-warning fingerprints. |
cmd/entire/cli/strategy/session_state_test.go |
Tests warning persistence and concurrency. |
cmd/entire/cli/strategy/lefthook_yaml.go |
Handles Lefthook configuration updates. |
cmd/entire/cli/strategy/lefthook_integration.go |
Coordinates Lefthook integration. |
cmd/entire/cli/strategy/lefthook_detection.go |
Detects Lefthook configurations and health. |
cmd/entire/cli/strategy/lefthook_artifacts.go |
Generates and validates Lefthook artifacts. |
cmd/entire/cli/strategy/hooks.go |
Implements Git hook operations. |
cmd/entire/cli/strategy/hooks_test.go |
Tests Git hook behavior. |
cmd/entire/cli/strategy/hook_managers.go |
Defines hook-manager integration behavior. |
cmd/entire/cli/strategy/hook_managers_test.go |
Tests hook-manager behavior. |
cmd/entire/cli/strategy/hook_integration.go |
Classifies hook integration health. |
cmd/entire/cli/strategy/common.go |
Provides shared setup and repair orchestration. |
cmd/entire/cli/setup.go |
Integrates hook setup with CLI setup flows. |
cmd/entire/cli/session/state.go |
Supports read-only session reporting. |
cmd/entire/cli/session/state_test.go |
Tests session state behavior. |
cmd/entire/cli/integration_test/status_health_test.go |
Tests status and sync-health reporting. |
cmd/entire/cli/integration_test/local_dev_migration_test.go |
Tests local development migration behavior. |
cmd/entire/cli/integration_test/lefthook_integration_test.go |
Tests Lefthook integration flows. |
cmd/entire/cli/integration_test/hooks_test.go |
Tests hook integration scenarios. |
cmd/entire/cli/integration_test/hook_overwrite_test.go |
Tests hook overwrite handling. |
cmd/entire/cli/hooks_git_cmd.go |
Handles Git hook command arguments. |
cmd/entire/cli/doctor.go |
Provides hook diagnostics and repair. |
cmd/entire/cli/doctor_test.go |
Tests doctor diagnostics. |
cmd/entire/cli/agent/geminicli/lifecycle.go |
Handles Gemini lifecycle reporting. |
cmd/entire/cli/agent/agent.go |
Integrates hook health into agent lifecycle behavior. |
Review details
Suppressed comments (4)
cmd/entire/cli/lifecycle.go:181
EnsureSetupis broader than hook repair: it can fail while updating.entire/.gitignore, initializing Vercel settings, opening the repo, or ensuring the primary metadata ref. This code converts any such failure intoGitHookIntegrationErroreven when the subsequent health check proves all five hooks are current, so TurnStart emits a false hook-health warning and fingerprints the wrong condition. Keep setup failures separate from hook-health classification (or return a hook-specific repair error) so a current integration remains current and unrelated setup failures are reported through their own path.
repairErr := ops.repair(ctx)
health := ops.check(ctx)
if repairErr != nil && health.State == strategy.GitHookIntegrationCurrent {
health.State = strategy.GitHookIntegrationError
health.ReasonCode = "hook_repair_failed"
}
cmd/entire/cli/lifecycle.go:830
- When SessionStart records an unhealthy fingerprint before durable session state exists, this unconditional transfer promotes that fingerprint into
LastHookHealthWarningeven ifrepairLifecycleHookHealthjust repaired the hooks and returned no warning. If the same hook failure recurs later in the same session,ClaimHookHealthWarningsees the old fingerprint and suppresses the warning forever. Consume/clear the pre-state hint on a successful repair (and only persist it as the last warning when the repaired health is still unhealthy), so recovery followed by recurrence can be reported again.
if err := strategy.TransferHookHealthWarningHint(ctx, sessionID); err != nil && !errors.Is(err, strategy.ErrStateNotFound) {
cmd/entire/cli/status.go:405
- When
checkpoint_push_remotenames a missing remote butcheckpoint_remoteis configured, this returnsErrand makescheckpoint_sync_stateblocked. However,PrePushbypasses the elected-remote gate wheneverPushURLsuccessfully derives the dedicated destination, so checkpoints can still be delivered on every push;statusthen reports the opposite of the actual sync path. Resolve/report the dedicated destination before failing on the missing elected remote (or otherwise carry both states) so health remains truthful for this supported exemption.
func computeCheckpointSyncInfo(ctx context.Context, s *EntireSettings, checkpointBackend string) checkpointSyncInfo {
elected, err := strategy.ResolveCheckpointSyncRemote(ctx)
if err != nil {
// Fail-closed: checkpoint_push_remote names a remote that does not
// exist. The pre-push gate is silently skipping checkpoint sync, so
// status is the user's signal.
// Accepted divergence: if a structured checkpoint_remote is also
// configured, the gate's dedicated exemption may still sync checkpoint
// data even while this fail-closed warning is shown, since there is no
// elected remote left to probe PushURL against here.
return checkpointSyncInfo{Err: err.Error()}
cmd/entire/cli/strategy/lefthook_detection.go:95
detectHookManagersForIntegrationdeliberately enumerates.tomland.jsoncas Lefthook main configs, so a repository with onlylefthook.tomlorlefthook.jsoncis selected here and then rejected unconditionally.EnsureGitHookIntegrationtherefore aborts before installing any native fallback, whileCheckGitHookIntegrationreports an inspection error even when the native Entire hooks are already current. Either support these Lefthook formats in the safe integration path or treat an unsupported format as non-integrable and preserve/report the working native integration instead of blocking setup and status.
ext := strings.ToLower(filepath.Ext(name))
if ext == ".toml" || ext == ".jsonc" {
return fmt.Errorf("lefthook config format %s is unsupported for safe source-directory inspection", ext)
- Files reviewed: 37/37 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| Use: "pre-push <remote> [remote-url]", | ||
| Short: "Handle pre-push git hook", | ||
| Args: cobra.ExactArgs(1), | ||
| Args: cobra.RangeArgs(1, 2), |
Entire-Checkpoint: 01M24BJBM0GWCV9BZ1BHBZT784
Entire-Checkpoint: 01M24E35K7XAGQK0557PTKTYCD
Entire-Checkpoint: 01M24J3PW87EJGK3XBWDAK2SDQ
Entire-Checkpoint: 01M24KK4BXHY6C4D09RB925ACB
Entire-Checkpoint: 01M24P0GM5H2XV15NPNHT3MTZZ
Entire-Checkpoint: 01M262A4WPJDHD696BKB482ZEE
Entire-Checkpoint: 01M267YFTF5DD35PEVV4ATCFW1
|
Superseded and closing: the two things this PR did are now split so each is reviewable on its own.
What this PR's title claimed is worth noting for the record: it says "make checkpoint status reflect hook health", but the bulk of the diff was the durable-hooks work, and it does not fix #2237 — the diff touches no Two defects found while rebuilding that were not in this PR's design, and are fixed in #2388: the integration as designed here made every git hook run Entire twice (its own hook, then Lefthook's launcher via the chain call — measured 2× |


https://entire.io/gh/entireio/cli/trails/1284
Problem
Issue #2264 reports that
entire statussays checkpoints sync toorigineven when Entire git hooks are missing, so capture and push cannot happen. The immediate bug is a false healthy status; the underlying cause in the reproduction is Lefthook overwriting the Entire wrapper duringlefthook install -for an npm postinstall.This PR addresses both layers: it makes status reflect actual hook delivery health, and it installs a durable Lefthook integration so refreshes do not silently remove checkpoint capture. It also emits lifecycle warnings when delivery is unhealthy, so users do not have to remember to run
entire statusto discover a failure. The session-listing and storage-versus-sync changes make the same status snapshot complete and unambiguous.Summary
Verification
Fixes #2264
Note
Medium Risk
Changes how Git hooks are installed, repaired, and reported across commits and push; mistakes could miss checkpoints or mislead sync readiness, but behavior is heavily tested and fails open on warnings.
Overview
This PR makes Git hook health a first-class part of checkpoint capture and sync reporting, with durable native and Lefthook integrations that survive manager refreshes instead of waiting for the next agent turn.
Hook integration:
CheckGitHookIntegration/EnsureGitHookIntegration/RemoveGitHookIntegrationreplace the older install-only checks. Lefthook repos get ownedlefthook-localscripts and config; native hooks remain the bridge when wrappers are missing.entire doctor, enable/setup, andEnsureSetupall heal through the same coordinator and surface DEGRADED / manager-specific reasons.Lifecycle: Turn start runs hook repair, emits a deduplicated
entire doctorwarning (fingerprinted in session state), and folds it into a single stdout payload for Claude/Codex JSON or a standalone plain-text path for Gemini (deferring context injection when both would collide). Session start can append the same warning to the banner.Status & sessions: Text and JSON share
buildStatusSnapshotwithcheckpoint_sync_state(ready/degraded/blocked),git_hookshealth, and storage backend. Sync is not advertised as healthy when hooks are absent or broken.entire statuslists every active session read-only (no stale cleanup or finalize-on-read) with richer JSON fields. pre-push accepts an optional remote URL argument.Reviewed by Cursor Bugbot for commit 598bc4f. Configure here.