Skip to content

fix: make checkpoint status reflect hook health - #2343

Closed
peyton-alt wants to merge 8 commits into
mainfrom
support
Closed

fix: make checkpoint status reflect hook health#2343
peyton-alt wants to merge 8 commits into
mainfrom
support

Conversation

@peyton-alt

@peyton-alt peyton-alt commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

https://entire.io/gh/entireio/cli/trails/1284

Problem

Issue #2264 reports that entire status says checkpoints sync to origin even 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 during lefthook install -f or 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 status to discover a failure. The session-listing and storage-versus-sync changes make the same status snapshot complete and unambiguous.

Summary

  • make native and Lefthook checkpoint-hook installation durable, ownership-aware, and transactional
  • surface hook delivery failures proactively and share truthful health diagnostics across status and doctor
  • separate storage from sync readiness and keep session reporting complete and read-only

Verification

  • mise run check
  • real Codex OAuth E2E: TestLefthookRefreshKeepsAgentCommitCheckpointed
  • real Claude Code OAuth E2E: TestLefthookRefreshKeepsAgentCommitCheckpointed
  • independent implementation review approved with no remaining actionable findings

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 / RemoveGitHookIntegration replace the older install-only checks. Lefthook repos get owned lefthook-local scripts and config; native hooks remain the bridge when wrappers are missing. entire doctor, enable/setup, and EnsureSetup all heal through the same coordinator and surface DEGRADED / manager-specific reasons.

Lifecycle: Turn start runs hook repair, emits a deduplicated entire doctor warning (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 buildStatusSnapshot with checkpoint_sync_state (ready / degraded / blocked), git_hooks health, and storage backend. Sync is not advertised as healthy when hooks are absent or broken. entire status lists 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.

@peyton-alt
peyton-alt requested a review from a team as a code owner September 9, 2026 23:57
Copilot AI lite review requested due to automatic review settings September 9, 2026 23:57

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 2 potential issues.

Fix All in Cursor

❌ 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"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

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)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 598bc4f. Configure here.

Entire-Checkpoint: 01M249K6A6PDMRNWJNP2A7CSE4

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 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

  • EnsureSetup is 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 into GitHookIntegrationError even 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 LastHookHealthWarning even if repairLifecycleHookHealth just repaired the hooks and returned no warning. If the same hook failure recurs later in the same session, ClaimHookHealthWarning sees 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_remote names a missing remote but checkpoint_remote is configured, this returns Err and makes checkpoint_sync_state blocked. However, PrePush bypasses the elected-remote gate whenever PushURL successfully derives the dedicated destination, so checkpoints can still be delivered on every push; status then 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

  • detectHookManagersForIntegration deliberately enumerates .toml and .jsonc as Lefthook main configs, so a repository with only lefthook.toml or lefthook.jsonc is selected here and then rejected unconditionally. EnsureGitHookIntegration therefore aborts before installing any native fallback, while CheckGitHookIntegration reports 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.

Comment on lines +303 to +305
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
@peyton-alt

Copy link
Copy Markdown
Contributor Author

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 backupExists/.pre-entire line in hooks.go and leaves TestInstallGitHook_DoesNotOverwriteExistingBackup untouched.

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× prepare-commit-msg/commit-msg/post-commit per commit), and LefthookManaged trusted lefthook-local.yml — a file Entire creates itself, so a repo stayed classified Lefthook-managed after Lefthook was removed.

@peyton-alt peyton-alt closed this Sep 11, 2026
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.

entire status reports "Checkpoints sync to: origin" when the git hooks are not installed

2 participants