From 08fcc3dc0776ea7493dfc112f378beffd469667a Mon Sep 17 00:00:00 2001 From: evisdren Date: Fri, 27 Feb 2026 16:33:13 -0800 Subject: [PATCH 1/3] Skip fully-condensed ENDED sessions in PostCommit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PostCommit iterates all sessions for the worktree, including ENDED sessions that have already been condensed with no remaining carry-forward files. Each session costs ~60ms just to exist in the pipeline (shadow resolve + hasNewContent + state machine + attribution), so 200 settled sessions add ~12s of pure overhead on every commit. Add a `FullyCondensed` flag to session state that gets set when an ENDED session is successfully condensed with no files remaining for carry-forward. PostCommit skips these sessions entirely, reducing steady-state commit overhead from seconds to milliseconds. The sessions still persist for LastCheckpointID reuse (amend trailer restoration). Benchmark results (2nd commit after sessions are condensed): - 100 sessions: 6.4s → 1.6s (3.9x) - 200 sessions: 14.5s → 3.3s (4.5x) - 500 sessions: 46.8s → 8.7s (5.4x) PostCommit specifically: 200 sessions dropped from 12.9s → 1.6s (8x). Co-Authored-By: Claude Opus 4.6 Entire-Checkpoint: 28481f123e69 --- cmd/entire/cli/session/state.go | 7 +++++++ cmd/entire/cli/strategy/manual_commit_hooks.go | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/cmd/entire/cli/session/state.go b/cmd/entire/cli/session/state.go index d082375b6..6dae21a91 100644 --- a/cmd/entire/cli/session/state.go +++ b/cmd/entire/cli/session/state.go @@ -114,6 +114,13 @@ type State struct { // sessions that have been condensed at least once. Cleared on new prompt. LastCheckpointID id.CheckpointID `json:"last_checkpoint_id,omitempty"` + // FullyCondensed indicates this session has been condensed and has no remaining + // carry-forward files. PostCommit skips fully-condensed sessions entirely. + // Set after successful condensation when no files remain for carry-forward + // and the session phase is ENDED. ENDED is terminal, so this flag is never + // reset — fully-condensed sessions only persist for LastCheckpointID reuse. + FullyCondensed bool `json:"fully_condensed,omitempty"` + // AgentType identifies the agent that created this session (e.g., "Claude Code", "Gemini CLI", "Cursor") AgentType types.AgentType `json:"agent_type,omitempty"` diff --git a/cmd/entire/cli/strategy/manual_commit_hooks.go b/cmd/entire/cli/strategy/manual_commit_hooks.go index 185fa0d4f..a9ec0786a 100644 --- a/cmd/entire/cli/strategy/manual_commit_hooks.go +++ b/cmd/entire/cli/strategy/manual_commit_hooks.go @@ -780,6 +780,11 @@ func (s *ManualCommitStrategy) PostCommit(ctx context.Context) error { //nolint: committedFileSet := filesChangedInCommit(commit, headTree, parentTree) for _, state := range sessions { + // Skip fully-condensed ended sessions — no work remains. + // These sessions only persist for LastCheckpointID (amend trailer reuse). + if state.FullyCondensed { + continue + } s.postCommitProcessSession(ctx, repo, state, &transitionCtx, checkpointID, head, commit, newHead, headTree, parentTree, committedFileSet, shadowBranchesToDelete, uncondensedActiveOnBranch) @@ -947,6 +952,13 @@ func (s *ManualCommitStrategy) postCommitProcessSession( } } + // Mark ENDED sessions as fully condensed when no carry-forward remains. + // PostCommit will skip these sessions entirely on future commits. + // They persist only for LastCheckpointID (amend trailer restoration). + if handler.condensed && state.Phase == session.PhaseEnded && len(state.FilesTouched) == 0 { + state.FullyCondensed = true + } + // Save the updated state if err := s.saveSessionState(ctx, state); err != nil { logging.Warn(logCtx, "failed to update session state", From f9c6270a6a509a2bcef5e59defa5bbbb102e2f8d Mon Sep 17 00:00:00 2001 From: evisdren Date: Fri, 27 Feb 2026 16:40:33 -0800 Subject: [PATCH 2/3] Clear FullyCondensed flag on session reactivation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ENDED sessions can be reactivated via TurnStart (→ ACTIVE) or SessionStart (→ IDLE). Clear FullyCondensed in ActionClearEndedAt so reactivated sessions are not skipped by PostCommit. Co-Authored-By: Claude Opus 4.6 Entire-Checkpoint: ee02ccfb2489 --- cmd/entire/cli/session/phase.go | 1 + cmd/entire/cli/session/state.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/entire/cli/session/phase.go b/cmd/entire/cli/session/phase.go index 80ab88f28..599ef9b9e 100644 --- a/cmd/entire/cli/session/phase.go +++ b/cmd/entire/cli/session/phase.go @@ -322,6 +322,7 @@ func ApplyTransition(ctx context.Context, state *State, result TransitionResult, state.LastInteractionTime = &now case ActionClearEndedAt: state.EndedAt = nil + state.FullyCondensed = false // Strategy-specific actions: skip remaining after the first handler error. case ActionCondense: diff --git a/cmd/entire/cli/session/state.go b/cmd/entire/cli/session/state.go index 6dae21a91..1bfdfeadf 100644 --- a/cmd/entire/cli/session/state.go +++ b/cmd/entire/cli/session/state.go @@ -117,8 +117,8 @@ type State struct { // FullyCondensed indicates this session has been condensed and has no remaining // carry-forward files. PostCommit skips fully-condensed sessions entirely. // Set after successful condensation when no files remain for carry-forward - // and the session phase is ENDED. ENDED is terminal, so this flag is never - // reset — fully-condensed sessions only persist for LastCheckpointID reuse. + // and the session phase is ENDED. Cleared on session reactivation (ENDED → + // ACTIVE via TurnStart, or ENDED → IDLE via SessionStart) by ActionClearEndedAt. FullyCondensed bool `json:"fully_condensed,omitempty"` // AgentType identifies the agent that created this session (e.g., "Claude Code", "Gemini CLI", "Cursor") From 70cc27d349fc0e2765e74d40d54f75f5f3bd1878 Mon Sep 17 00:00:00 2001 From: Stefan Haubold Date: Sun, 1 Mar 2026 21:30:45 +0100 Subject: [PATCH 3/3] adding tests, small improvement / better guard Entire-Checkpoint: 1043cf5253ff --- .../integration_test/fully_condensed_test.go | 108 +++++++++++++ cmd/entire/cli/session/phase_test.go | 53 ++++-- .../cli/strategy/manual_commit_hooks.go | 6 +- .../cli/strategy/phase_postcommit_test.go | 151 ++++++++++++++++++ 4 files changed, 308 insertions(+), 10 deletions(-) create mode 100644 cmd/entire/cli/integration_test/fully_condensed_test.go diff --git a/cmd/entire/cli/integration_test/fully_condensed_test.go b/cmd/entire/cli/integration_test/fully_condensed_test.go new file mode 100644 index 000000000..eef49600f --- /dev/null +++ b/cmd/entire/cli/integration_test/fully_condensed_test.go @@ -0,0 +1,108 @@ +//go:build integration + +package integration + +import ( + "testing" + + "github.com/entireio/cli/cmd/entire/cli/session" +) + +// TestFullyCondensed_ReactivationClearsFlag tests that when a fully-condensed +// ENDED session is reactivated (new UserPromptSubmit), the FullyCondensed flag +// is cleared so the session is processed normally on future commits. +// +// This is the critical safety test: if FullyCondensed isn't cleared on +// reactivation, the session's new work would be silently skipped in PostCommit. +// +// State machine transitions tested: +// - IDLE + SessionStop -> ENDED +// - ENDED + GitCommit -> ENDED + ActionCondenseIfFilesTouched (sets FullyCondensed) +// - ENDED + TurnStart -> ACTIVE + ActionClearEndedAt (clears FullyCondensed) +func TestFullyCondensed_ReactivationClearsFlag(t *testing.T) { + t.Parallel() + + env := NewFeatureBranchEnv(t) + + // ======================================== + // Phase 1: Create session, do work, stop, end, then commit → FullyCondensed + // ======================================== + t.Log("Phase 1: Run a full session lifecycle and commit after ending") + + sess := env.NewSession() + + if err := env.SimulateUserPromptSubmit(sess.ID); err != nil { + t.Fatalf("user-prompt-submit failed: %v", err) + } + + env.WriteFile("feature.go", "package main\n\nfunc Feature() {}\n") + sess.CreateTranscript("Create feature function", []FileChange{ + {Path: "feature.go", Content: "package main\n\nfunc Feature() {}\n"}, + }) + + if err := env.SimulateStop(sess.ID, sess.TranscriptPath); err != nil { + t.Fatalf("SimulateStop failed: %v", err) + } + + // Verify IDLE with files touched + state, err := env.GetSessionState(sess.ID) + if err != nil { + t.Fatalf("GetSessionState failed: %v", err) + } + if state.Phase != session.PhaseIdle { + t.Fatalf("Expected IDLE after stop, got %s", state.Phase) + } + if len(state.FilesTouched) == 0 { + t.Fatal("FilesTouched should be non-empty after agent work") + } + + // End the session BEFORE committing — FullyCondensed is only set for ENDED sessions + if err := env.SimulateSessionEnd(sess.ID); err != nil { + t.Fatalf("SimulateSessionEnd failed: %v", err) + } + + state, err = env.GetSessionState(sess.ID) + if err != nil { + t.Fatalf("GetSessionState failed: %v", err) + } + if state.Phase != session.PhaseEnded { + t.Fatalf("Expected ENDED after session end, got %s", state.Phase) + } + + // Commit the work — PostCommit condenses the ENDED session with files touched, + // all files are committed so no carry-forward remains → FullyCondensed = true + env.GitCommitWithShadowHooks("Add feature", "feature.go") + + // Verify ENDED with FullyCondensed + state, err = env.GetSessionState(sess.ID) + if err != nil { + t.Fatalf("GetSessionState failed: %v", err) + } + if state.Phase != session.PhaseEnded { + t.Fatalf("Expected ENDED after commit, got %s", state.Phase) + } + if !state.FullyCondensed { + t.Fatal("Session should be FullyCondensed after condensation with no carry-forward") + } + + // ======================================== + // Phase 2: Reactivate the session → FullyCondensed should be cleared + // ======================================== + t.Log("Phase 2: Reactivate the ended session") + + if err := env.SimulateUserPromptSubmit(sess.ID); err != nil { + t.Fatalf("user-prompt-submit (reactivation) failed: %v", err) + } + + state, err = env.GetSessionState(sess.ID) + if err != nil { + t.Fatalf("GetSessionState failed: %v", err) + } + if state.Phase != session.PhaseActive { + t.Errorf("Expected ACTIVE after reactivation, got %s", state.Phase) + } + if state.FullyCondensed { + t.Error("FullyCondensed must be cleared on reactivation — " + + "otherwise new work would be silently skipped in PostCommit") + } +} diff --git a/cmd/entire/cli/session/phase_test.go b/cmd/entire/cli/session/phase_test.go index 558c791cc..6e4aae20d 100644 --- a/cmd/entire/cli/session/phase_test.go +++ b/cmd/entire/cli/session/phase_test.go @@ -503,18 +503,53 @@ func TestApplyTransition_CallsHandlerForWarnStaleSession(t *testing.T) { func TestApplyTransition_ClearsEndedAt(t *testing.T) { t.Parallel() - endedAt := time.Now().Add(-time.Hour) - state := &State{Phase: PhaseEnded, EndedAt: &endedAt} - handler := &mockActionHandler{} - result := TransitionResult{ - NewPhase: PhaseIdle, - Actions: []Action{ActionClearEndedAt}, + tests := []struct { + name string + fullyCondensed bool + newPhase Phase + actions []Action + }{ + { + name: "SessionStart_to_IDLE", + newPhase: PhaseIdle, + actions: []Action{ActionClearEndedAt}, + }, + { + name: "TurnStart_to_ACTIVE", + newPhase: PhaseActive, + actions: []Action{ActionClearEndedAt, ActionUpdateLastInteraction}, + }, + { + name: "TurnStart_clears_FullyCondensed", + fullyCondensed: true, + newPhase: PhaseActive, + actions: []Action{ActionClearEndedAt, ActionUpdateLastInteraction}, + }, + { + name: "SessionStart_clears_FullyCondensed", + fullyCondensed: true, + newPhase: PhaseIdle, + actions: []Action{ActionClearEndedAt}, + }, } - err := ApplyTransition(context.Background(), state, result, handler) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() - require.NoError(t, err) - assert.Nil(t, state.EndedAt) + endedAt := time.Now().Add(-time.Hour) + state := &State{Phase: PhaseEnded, EndedAt: &endedAt, FullyCondensed: tt.fullyCondensed} + handler := &mockActionHandler{} + result := TransitionResult{NewPhase: tt.newPhase, Actions: tt.actions} + + err := ApplyTransition(context.Background(), state, result, handler) + + require.NoError(t, err) + assert.Nil(t, state.EndedAt) + assert.False(t, state.FullyCondensed, + "FullyCondensed must be cleared when ActionClearEndedAt runs") + }) + } } func TestApplyTransition_ReturnsHandlerError_ButRunsCommonActions(t *testing.T) { diff --git a/cmd/entire/cli/strategy/manual_commit_hooks.go b/cmd/entire/cli/strategy/manual_commit_hooks.go index a9ec0786a..79c4faf7d 100644 --- a/cmd/entire/cli/strategy/manual_commit_hooks.go +++ b/cmd/entire/cli/strategy/manual_commit_hooks.go @@ -782,7 +782,7 @@ func (s *ManualCommitStrategy) PostCommit(ctx context.Context) error { //nolint: for _, state := range sessions { // Skip fully-condensed ended sessions — no work remains. // These sessions only persist for LastCheckpointID (amend trailer reuse). - if state.FullyCondensed { + if state.FullyCondensed && state.Phase == session.PhaseEnded { continue } s.postCommitProcessSession(ctx, repo, state, &transitionCtx, checkpointID, @@ -1105,6 +1105,10 @@ func (s *ManualCommitStrategy) filterSessionsWithNewContent(ctx context.Context, var result []*SessionState for _, state := range sessions { + // Skip fully-condensed ended sessions — no new content possible. + if state.FullyCondensed && state.Phase == session.PhaseEnded { + continue + } hasNew, err := s.sessionHasNewContent(ctx, repo, state) if err != nil { // On error, include the session (fail open for hooks) diff --git a/cmd/entire/cli/strategy/phase_postcommit_test.go b/cmd/entire/cli/strategy/phase_postcommit_test.go index 6efa60c8b..26e284108 100644 --- a/cmd/entire/cli/strategy/phase_postcommit_test.go +++ b/cmd/entire/cli/strategy/phase_postcommit_test.go @@ -1992,3 +1992,154 @@ func TestPostCommit_EndedSession_SkipsSentinelWait(t *testing.T) { require.NoError(t, err, "entire/checkpoints/v1 branch should exist after condensation") assert.NotNil(t, sessionsRef) } + +// TestPostCommit_EndedSession_SetsFullyCondensed verifies that an ENDED session +// is marked FullyCondensed after condensation when no carry-forward files remain. +func TestPostCommit_EndedSession_SetsFullyCondensed(t *testing.T) { + dir := setupGitRepo(t) + t.Chdir(dir) + + repo, err := git.PlainOpen(dir) + require.NoError(t, err) + + s := &ManualCommitStrategy{} + sessionID := "test-postcommit-ended-fully-condensed" + + // Initialize session and save a checkpoint + setupSessionWithCheckpoint(t, s, repo, dir, sessionID) + + // Set phase to ENDED with files touched (the committed file matches shadow branch) + state, err := s.loadSessionState(context.Background(), sessionID) + require.NoError(t, err) + now := time.Now() + state.Phase = session.PhaseEnded + state.EndedAt = &now + state.FilesTouched = []string{"test.txt"} + require.NoError(t, s.saveSessionState(context.Background(), state)) + + // Create a commit that includes test.txt — this commits the only touched file, + // so carry-forward will be empty afterward. + commitWithCheckpointTrailer(t, repo, dir, "fc01fc01fc01") + + // Run PostCommit + err = s.PostCommit(context.Background()) + require.NoError(t, err) + + // Verify FullyCondensed is set + state, err = s.loadSessionState(context.Background(), sessionID) + require.NoError(t, err) + assert.True(t, state.FullyCondensed, + "ENDED session with no carry-forward should be marked FullyCondensed") + assert.Equal(t, session.PhaseEnded, state.Phase) + assert.Empty(t, state.FilesTouched, + "FilesTouched should be empty after all files were committed") +} + +// TestPostCommit_FullyCondensedEndedSession_SkippedOnNextCommit verifies that +// a FullyCondensed ENDED session is skipped entirely on subsequent commits, +// avoiding redundant shadow branch resolution and condensation attempts. +func TestPostCommit_FullyCondensedEndedSession_SkippedOnNextCommit(t *testing.T) { + dir := setupGitRepo(t) + t.Chdir(dir) + + repo, err := git.PlainOpen(dir) + require.NoError(t, err) + + s := &ManualCommitStrategy{} + sessionID := "test-postcommit-skip-fully-condensed" + + // Initialize session and save a checkpoint + setupSessionWithCheckpoint(t, s, repo, dir, sessionID) + + // Set phase to ENDED with files touched + state, err := s.loadSessionState(context.Background(), sessionID) + require.NoError(t, err) + now := time.Now() + state.Phase = session.PhaseEnded + state.EndedAt = &now + state.FilesTouched = []string{"test.txt"} + require.NoError(t, s.saveSessionState(context.Background(), state)) + + // First commit — condenses the ENDED session and marks it FullyCondensed + commitWithCheckpointTrailer(t, repo, dir, "fc02fc02fc02") + err = s.PostCommit(context.Background()) + require.NoError(t, err) + + // Verify it's now fully condensed + state, err = s.loadSessionState(context.Background(), sessionID) + require.NoError(t, err) + require.True(t, state.FullyCondensed) + + // Record the LastCheckpointID — this should persist (the reason the session exists) + lastCPID := state.LastCheckpointID + + // Second commit — the fully-condensed session should be skipped entirely. + // Create a new file so there's something to commit. + require.NoError(t, os.WriteFile(filepath.Join(dir, "other.txt"), []byte("other"), 0o644)) + wt, err := repo.Worktree() + require.NoError(t, err) + _, err = wt.Add("other.txt") + require.NoError(t, err) + commitMsg := "second commit\n\n" + trailers.CheckpointTrailerKey + ": fc03fc03fc03\n" + _, err = wt.Commit(commitMsg, &git.CommitOptions{ + Author: &object.Signature{ + Name: "Test", + Email: "test@test.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + // Run PostCommit again + err = s.PostCommit(context.Background()) + require.NoError(t, err) + + // Verify state is unchanged — the session was skipped, not re-processed + state, err = s.loadSessionState(context.Background(), sessionID) + require.NoError(t, err) + assert.True(t, state.FullyCondensed, + "FullyCondensed should still be true after being skipped") + assert.Equal(t, session.PhaseEnded, state.Phase) + assert.Equal(t, lastCPID, state.LastCheckpointID, + "LastCheckpointID should be preserved across skipped commits") +} + +// TestPostCommit_NonEndedSession_NotMarkedFullyCondensed verifies that ACTIVE +// and IDLE sessions are never marked FullyCondensed, even when condensed with +// no carry-forward. Only ENDED sessions get the flag. +func TestPostCommit_NonEndedSession_NotMarkedFullyCondensed(t *testing.T) { + for _, phase := range []session.Phase{session.PhaseActive, session.PhaseIdle} { + t.Run(string(phase), func(t *testing.T) { + dir := setupGitRepo(t) + t.Chdir(dir) + + repo, err := git.PlainOpen(dir) + require.NoError(t, err) + + s := &ManualCommitStrategy{} + sessionID := "test-postcommit-" + string(phase) + "-not-fully-condensed" + + // Initialize session and save a checkpoint + setupSessionWithCheckpoint(t, s, repo, dir, sessionID) + + state, err := s.loadSessionState(context.Background(), sessionID) + require.NoError(t, err) + state.Phase = phase + state.FilesTouched = []string{"test.txt"} + require.NoError(t, s.saveSessionState(context.Background(), state)) + + // Commit the file + commitWithCheckpointTrailer(t, repo, dir, "fc04fc04fc04") + + // Run PostCommit + err = s.PostCommit(context.Background()) + require.NoError(t, err) + + // Verify FullyCondensed is NOT set + state, err = s.loadSessionState(context.Background(), sessionID) + require.NoError(t, err) + assert.False(t, state.FullyCondensed, + "%s sessions must never be marked FullyCondensed", phase) + }) + } +}