Skip to content

fix(strategy): create checkpoints for commits during an active session - #1728

Closed
entire[bot] wants to merge 4 commits into
mainfrom
fix/487-active-session-checkpoints
Closed

fix(strategy): create checkpoints for commits during an active session#1728
entire[bot] wants to merge 4 commits into
mainfrom
fix/487-active-session-checkpoints

Conversation

@entire

@entire entire Bot commented Jul 12, 2026

Copy link
Copy Markdown

Trail: https://entire.io/gh/entireio/cli/trails/835

Summary

Commits made during an active agent session produced 0 checkpoints whenever the commit reached post-commit without an Entire-Checkpoint trailer. This fixes that data loss.

Root cause

PostCommit gated all session processing on the Entire-Checkpoint trailer that PrepareCommitMsg adds. When PrepareCommitMsg does not run — a known failure mode when a global core.hooksPath is configured or entire is off the git hook's PATH, so only post-commit fires — the commit carries no trailer, PostCommit returned early (updating BaseCommit only), and the ACTIVE session's work for that commit was lost.

The documented state machine mandates ACTIVE + GitCommit -> Condense, but that transition was never reached. The Stop hook cannot recover it either: the files are already committed, so nothing is "modified" (no files modified during session, skipping checkpoint). The result is entire/checkpoints/v1 keeping only its metadata-init commit — exactly the reported symptom.

Fix

PostCommit now recovers a trailerless commit instead of dropping it. With no trailer, if the commit was made non-interactively (agent, no TTY) and a recent ACTIVE session's tracked work content-overlaps the committed files, it generates a checkpoint ID and condenses via the normal EventGitCommit -> Condense path. The recovered checkpoint is finalized with the full transcript at Stop, like any mid-turn checkpoint.

Guardrails preserve existing behavior:

  • No-TTY only — a human at a terminal with no trailer may have declined linking; that intent is honored, so interactive commits are never recovered.
  • Content-overlap required — the ACTIVE-session overlap-skip in shouldCondenseWithOverlapCheck is disabled on the recovery path, so an unrelated agent/CI commit made while a session is active is not falsely linked.

The trailer that links commit↔checkpoint cannot be added to an already-made commit, so recovery preserves the work on entire/checkpoints/v1 (no data loss); a commit-anchored entire explain <commit> still won't associate a trailerless commit. Documented as caveat #10 in docs/architecture/checkpoint-scenarios.md.

Testing

Live reproduction with real git hooks (built binary on PATH) and real git commits, neutralizing prepare-commit-msg so mid-session commits carry no trailer.

Before (current behavior):

== after mid-session commit ==   HEAD trailer: (none)
   entire/checkpoints/v1:  Initialize metadata ref            <-- 0 checkpoints
== after stop ==   "no files modified during session, skipping checkpoint"

After (this change):

== after mid-session commit ==   HEAD trailer: (none)
   entire/checkpoints/v1:  Checkpoint: 74dd40ff8ab0
                           Initialize metadata ref            <-- recovered
== after stop ==   Finalize transcript for Checkpoint: 74dd40ff8ab0

Live edge-case matrix (all pass):

  • happy path (working prepare-commit-msg, trailered) — unchanged, no regression
  • unrelated-file trailerless commit — not recovered
  • multiple mid-session commits — each recovers its own checkpoint
  • post-stop (IDLE) commit — not recovered
  • amend during session (post-rewrite) — trailer preserved
  • concurrent second session — interleaving unaffected

Gates:

  • mise run lint — 0 issues
  • strategy + session unit tests — 892 pass
  • mid-session integration tests — 10 pass (4 new: recovery, unrelated non-recovery, IDLE non-recovery, multiple commits)
  • vogon E2E canary — single/multi/subagent commit-in-turn scenarios pass with the canonical vogon agent

Audit — attribution hardening

An adversarial audit of the recovery path (focused on wrong-session attribution
and over-recovery) found the fix correct; no production-code change was needed. A
follow-up commit adds a mutation-verified regression test and tightens the overlap
docs.

Wrong-attribution verdict. Recovery never links more sessions than the normal
trailer path, and is strictly more precise for disjoint concurrent sessions:

Concurrent-session scenario Trailer path (baseline) Recovery path
Two ACTIVE sessions, disjoint files, one file committed both condensed (overlap check skipped) only the owning session (overlap-gated)
Two ACTIVE sessions, same modified file committed both condensed both condensed (inherent; identical to baseline)
Unrelated trailerless commit (no tracked-file overlap) n/a not recovered

Recovery disables the ACTIVE-session overlap-skip, so it requires the committed
paths to intersect the session's tracked files. Overlap is content-exact for
newly-added files (blob-hash match against the shadow branch) and path-based for
modified files — the same heuristic PostCommit uses elsewhere. A single-session
false positive (an unrelated actor modifying a pre-existing file the session also
touched) preserves the session's work with no data loss and is baseline-equivalent
to the trailer path.

Also verified: trailer-present and recovery are mutually exclusive (found vs
!found), so there is no double-checkpoint; repeated post-commit on the same
trailerless commit is idempotent (carry-forward clears the tracked files); amend of
a recovered commit does not runaway-duplicate; generating a recovery checkpoint ID
when no session ends up condensing writes no orphan (updateCombinedAttribution
no-ops on a missing summary); and the no-TTY gate is the correct proxy — the narrow
agent-env signal (isAgentSubprocessEnv) misses Claude Code/Codex/Cursor, so no-TTY
is what actually targets the reported scenario.

New test (mutation-verified):
TestShadowStrategy_MidSessionCommit_ConcurrentDisjoint_OnlyOwningSessionRecovered
— asserts a trailerless commit of one session's file recovers only that session and
does not cross-attribute a concurrent disjoint session. Verified by mutation:
dropping the noTrailerRecovery overlap gate makes it fail with both sessions
attributed to the checkpoint. The three existing #487 non-recovery/recovery tests
were also mutation-checked (neutralizing recovery, loosening the overlap gate, and
removing the ACTIVE-session requirement each fail exactly the intended test).

Gates: gofmt clean; golangci-lint reports 0 issues on the strategy package and
no new issues from the added test; strategy unit + mid-session integration tests
green. The full mise run check / vogon E2E canary depends on the
roger-roger + tmux external binaries (pre-existing, environment-gated); the
vogon deterministic mid-turn-commit and trailer-removal scenarios run green.

Follow-up — human-typed commit in broken-hooks mode

Re-analysis surfaced a false-negative the original guardrail missed. The recovery
gated on no-TTY as a proxy for "agent commit", so any commit made at a TTY was
treated as a possibly-declined link and never recovered. But the #487 root cause
(a global core.hooksPath, or Entire off the hook PATH) stops
prepare-commit-msg from running for both agent and human-typed commits, and
no link prompt ever appears — so a developer typing git commit at a terminal
while an agent's ACTIVE session had overlapping uncommitted work still silently
lost it.

Contract decision. Recovery is suppressed only for a genuine decline: a TTY
commit made while Entire's prepare-commit-msg hook was actually wired
(PrepareCommitMsgHookWired resolves the hooks dir git uses, respecting
core.hooksPath, and checks for Entire's hook). Wired + TTY + no trailer ⇒ the
human answered "no" to the prompt or deleted the offered trailer in their editor
⇒ honored. Not wired (broken-hooks mode) ⇒ no linking was ever offered ⇒ a
missing trailer is not a decline ⇒ the overlapping active-session work is
recovered for human-typed and agent commits alike. Agent commits have no TTY and
stay eligible regardless.

Hook-wiring detection was chosen over an explicit "declined prompt" marker
because it also honors the editor-flow trailer deletion (which never calls
askConfirmTTY) and is stateless — no cross-hook marker to persist, go stale, or
scope per worktree. Mutation-verified: reverting to the no-TTY-only gate breaks
the broken-hooks-human recovery test; making suppression never fire breaks the
wired-hook decline test. Documented in checkpoint-scenarios.md caveat #10.

Fixes #487

Commits made during an active agent session produced 0 checkpoints when the
commit reached post-commit without an Entire-Checkpoint trailer.

Root cause: PostCommit gated all session processing on the presence of the
Entire-Checkpoint trailer that PrepareCommitMsg adds. When PrepareCommitMsg
does not run -- a known failure mode when a global core.hooksPath is set or
`entire` is off the git hook's PATH, so only post-commit fires -- the commit
carries no trailer, PostCommit returned early (updating BaseCommit only), and
the ACTIVE session's work for that commit was lost. The state machine mandates
ACTIVE + GitCommit -> Condense, but that transition was never reached. The Stop
hook cannot recover it either: the files are already committed, so nothing is
"modified" ("no files modified during session, skipping checkpoint"). Result:
entire/checkpoints/v1 keeps only its metadata-init commit.

Fix: PostCommit now recovers a trailerless commit instead of dropping it. When
the commit has no trailer, was made non-interactively (agent, no TTY), and a
recent ACTIVE session's tracked work content-overlaps the committed files, it
generates a checkpoint ID and condenses via the normal EventGitCommit ->
Condense path; the recovered checkpoint is finalized with the full transcript
at Stop, like any mid-turn checkpoint. Guardrails preserve existing behavior:

- No-TTY only: a human at a terminal with no trailer may have declined linking;
  that intent is honored, so interactive commits are never recovered.
- Content-overlap required: the ACTIVE-session overlap-skip in
  shouldCondenseWithOverlapCheck is disabled on the recovery path, so an
  unrelated agent/CI commit made while a session is active is not falsely linked.

Contract: docs Scenario 2 says a mid-session commit SHOULD checkpoint. The
trailer that links commit<->checkpoint is added by PrepareCommitMsg and cannot
be added to an already-made commit, so recovery preserves the work on
entire/checkpoints/v1 (no data loss) even though a commit-anchored
`entire explain <commit>` won't associate a trailerless commit. Documented as
caveat #10 in checkpoint-scenarios.md.

Tests: integration coverage for recovery, the unrelated-file and post-stop
(IDLE) non-recovery gates, and multiple trailerless commits each recovering.

Fixes #487
@suhaanthayyil
suhaanthayyil marked this pull request as ready for review July 12, 2026 01:52
@suhaanthayyil
suhaanthayyil requested a review from a team as a code owner July 12, 2026 01:52
Copilot AI review requested due to automatic review settings July 12, 2026 01:52

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.

Pull request overview

This PR fixes a data-loss gap in the manual-commit strategy where commits made during an active agent session could produce 0 checkpoints if the commit reached post-commit without an Entire-Checkpoint trailer (e.g., prepare-commit-msg didn’t run due to core.hooksPath or PATH issues). The strategy now attempts a narrow recovery path for trailerless, non-interactive commits by condensing session work when it content-overlaps the committed files, and documents the resulting caveat around commit-anchored entire explain <commit>.

Changes:

  • Add trailerless mid-session commit recovery in PostCommit, including an overlap-check guardrail for ACTIVE sessions when recovering.
  • Add integration test helpers and new mid-session integration tests covering recovery, non-recovery cases, and multiple trailerless commits.
  • Document the recovery behavior and limitations in checkpoint scenario docs.

Reviewed changes

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

File Description
docs/architecture/checkpoint-scenarios.md Documents trailerless mid-session commit recovery behavior and limitations.
cmd/entire/cli/strategy/manual_commit_hooks.go Implements recovery for trailerless mid-session commits and tightens overlap gating during recovery.
cmd/entire/cli/integration_test/testenv.go Adds helper to create trailerless agent commits and a utility to count checkpoint commits.
cmd/entire/cli/integration_test/mid_session_commit_test.go Adds regression/behavior tests for trailerless commit recovery and guardrails.

Comment thread cmd/entire/cli/integration_test/testenv.go
Comment thread cmd/entire/cli/strategy/manual_commit_hooks.go Outdated
… overlap docs

Audit hardening for the trailerless mid-session commit recovery (issue #487).
No production-code change: the recovery in PostCommit was verified correct
(reaches ACTIVE + GitCommit -> Condense, finalizes at Stop, is idempotent and
amend-safe, and is mutually exclusive with the trailer path so it cannot
double-checkpoint). These changes strengthen the test coverage and docs for the
wrong-attribution risk area.

- Add TestShadowStrategy_MidSessionCommit_ConcurrentDisjoint_OnlyOwningSessionRecovered:
  two concurrent ACTIVE sessions with disjoint tracked files; a trailerless commit
  of one session's file must recover ONLY that session and never cross-attribute the
  other. The recovery path's content-overlap gate makes this strictly more precise
  than the normal trailer path, which condenses every recent ACTIVE session
  regardless of overlap. Mutation-verified: dropping the noTrailerRecovery overlap
  gate makes the test fail with both sessions attributed to the checkpoint.

- Clarify checkpoint-scenarios.md caveat #10: recovery overlap is the intersection
  of committed paths with the session's tracked files, verified content-exact for
  newly-added files (blob-hash match against the shadow branch) and by path for
  modified files -- the same heuristic PostCommit uses elsewhere. Documents that a
  commit touching only untracked files is left alone, a modified pre-existing file
  is linked even if bytes differ, and two concurrent sessions editing the same
  committed file are both linked (as in the trailer path).
…s mode (#487)

The #487 recovery gated on no-TTY as a proxy for "agent commit", treating any
commit made at a TTY as a possibly-declined link and never recovering it. But the
#487 root cause -- a global core.hooksPath, or entire off the git hook PATH --
stops prepare-commit-msg from running for BOTH agent and human-typed commits, and
no link prompt ever appears. A developer typing `git commit` at a terminal while
an agent's ACTIVE session had overlapping uncommitted work therefore still lost
that work: TTY presence was wrongly read as a decline.

Fix: suppress recovery only for a genuine decline -- a TTY commit made while
Entire's prepare-commit-msg hook was actually wired. PrepareCommitMsgHookWired
resolves the hooks dir git uses (respecting core.hooksPath) and checks for
Entire's prepare-commit-msg. When it is wired, an absent trailer means the human
answered "no" to the prompt or deleted the offered trailer in their editor, which
is honored. When it is NOT wired (the #487 broken-hooks mode), no linking was ever
offered, so a missing trailer is not a decline and the ACTIVE session's
overlapping work is recovered for human-typed and agent commits alike. Agent
commits have no TTY and stay eligible regardless.

Hook-wiring is a stronger signal than a "declined prompt" marker: it also honors
the editor-flow trailer deletion (which never calls askConfirmTTY), and it is
stateless -- no cross-hook marker to persist, go stale, or scope per worktree.

Tests: a human-TTY commit in broken-hooks mode now recovers; a TTY commit with the
prepare-commit-msg hook wired (a real decline) is still not recovered; unit test
for PrepareCommitMsgHookWired. Both integration tests are mutation-verified.
Contract documented in checkpoint-scenarios.md caveat #10.
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.

auto-commit: 0 checkpoints created during active Claude Code session

2 participants