From ba20a2f0a592ef5194e88fb9660d89b29dc5b0d1 Mon Sep 17 00:00:00 2001 From: Stefan Haubold Date: Fri, 27 Feb 2026 19:12:39 +0100 Subject: [PATCH 1/3] don't update LastInteraction when only git hooks were triggered Entire-Checkpoint: dd07617714d9 --- cmd/entire/cli/session/phase.go | 8 ++++---- cmd/entire/cli/session/phase_test.go | 26 +++++++++++--------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/cmd/entire/cli/session/phase.go b/cmd/entire/cli/session/phase.go index 80ab88f28..088eaf74e 100644 --- a/cmd/entire/cli/session/phase.go +++ b/cmd/entire/cli/session/phase.go @@ -168,7 +168,7 @@ func transitionFromIdle(event Event, ctx TransitionContext) TransitionResult { } return TransitionResult{ NewPhase: PhaseIdle, - Actions: []Action{ActionCondense, ActionUpdateLastInteraction}, + Actions: []Action{ActionCondense}, } case EventSessionStart: // Already condensable, no-op. @@ -208,7 +208,7 @@ func transitionFromActive(event Event, ctx TransitionContext) TransitionResult { } return TransitionResult{ NewPhase: PhaseActive, - Actions: []Action{ActionCondense, ActionUpdateLastInteraction}, + Actions: []Action{ActionCondense}, } case EventSessionStart: return TransitionResult{ @@ -249,12 +249,12 @@ func transitionFromEnded(event Event, ctx TransitionContext) TransitionResult { if ctx.HasFilesTouched { return TransitionResult{ NewPhase: PhaseEnded, - Actions: []Action{ActionCondenseIfFilesTouched, ActionUpdateLastInteraction}, + Actions: []Action{ActionCondenseIfFilesTouched}, } } return TransitionResult{ NewPhase: PhaseEnded, - Actions: []Action{ActionDiscardIfNoFiles, ActionUpdateLastInteraction}, + Actions: []Action{ActionDiscardIfNoFiles}, } case EventSessionStart: return TransitionResult{ diff --git a/cmd/entire/cli/session/phase_test.go b/cmd/entire/cli/session/phase_test.go index 558c791cc..e96a6c779 100644 --- a/cmd/entire/cli/session/phase_test.go +++ b/cmd/entire/cli/session/phase_test.go @@ -140,7 +140,7 @@ func TestTransitionFromIdle(t *testing.T) { current: PhaseIdle, event: EventGitCommit, wantPhase: PhaseIdle, - wantActions: []Action{ActionCondense, ActionUpdateLastInteraction}, + wantActions: []Action{ActionCondense}, }, { name: "GitCommit_rebase_skips_everything", @@ -196,7 +196,7 @@ func TestTransitionFromActive(t *testing.T) { current: PhaseActive, event: EventGitCommit, wantPhase: PhaseActive, - wantActions: []Action{ActionCondense, ActionUpdateLastInteraction}, + wantActions: []Action{ActionCondense}, }, { name: "GitCommit_rebase_skips_everything", @@ -239,14 +239,14 @@ func TestTransitionFromEnded(t *testing.T) { event: EventGitCommit, ctx: TransitionContext{HasFilesTouched: true}, wantPhase: PhaseEnded, - wantActions: []Action{ActionCondenseIfFilesTouched, ActionUpdateLastInteraction}, + wantActions: []Action{ActionCondenseIfFilesTouched}, }, { name: "GitCommit_without_files_discards", current: PhaseEnded, event: EventGitCommit, wantPhase: PhaseEnded, - wantActions: []Action{ActionDiscardIfNoFiles, ActionUpdateLastInteraction}, + wantActions: []Action{ActionDiscardIfNoFiles}, }, { name: "GitCommit_rebase_skips_everything", @@ -295,7 +295,7 @@ func TestTransitionBackwardCompat(t *testing.T) { current: Phase(""), event: EventGitCommit, wantPhase: PhaseIdle, - wantActions: []Action{ActionCondense, ActionUpdateLastInteraction}, + wantActions: []Action{ActionCondense}, }, { name: "empty_phase_SessionStop_treated_as_IDLE", @@ -441,7 +441,7 @@ func TestApplyTransition_CallsHandlerForCondense(t *testing.T) { handler := &mockActionHandler{} result := TransitionResult{ NewPhase: PhaseIdle, - Actions: []Action{ActionCondense, ActionUpdateLastInteraction}, + Actions: []Action{ActionCondense}, } err := ApplyTransition(context.Background(), state, result, handler) @@ -449,7 +449,6 @@ func TestApplyTransition_CallsHandlerForCondense(t *testing.T) { require.NoError(t, err) assert.True(t, handler.condenseCalled) assert.Equal(t, PhaseIdle, state.Phase) - require.NotNil(t, state.LastInteractionTime) } func TestApplyTransition_CallsHandlerForCondenseIfFilesTouched(t *testing.T) { @@ -459,7 +458,7 @@ func TestApplyTransition_CallsHandlerForCondenseIfFilesTouched(t *testing.T) { handler := &mockActionHandler{} result := TransitionResult{ NewPhase: PhaseEnded, - Actions: []Action{ActionCondenseIfFilesTouched, ActionUpdateLastInteraction}, + Actions: []Action{ActionCondenseIfFilesTouched}, } err := ApplyTransition(context.Background(), state, result, handler) @@ -475,7 +474,7 @@ func TestApplyTransition_CallsHandlerForDiscardIfNoFiles(t *testing.T) { handler := &mockActionHandler{} result := TransitionResult{ NewPhase: PhaseEnded, - Actions: []Action{ActionDiscardIfNoFiles, ActionUpdateLastInteraction}, + Actions: []Action{ActionDiscardIfNoFiles}, } err := ApplyTransition(context.Background(), state, result, handler) @@ -517,25 +516,22 @@ func TestApplyTransition_ClearsEndedAt(t *testing.T) { assert.Nil(t, state.EndedAt) } -func TestApplyTransition_ReturnsHandlerError_ButRunsCommonActions(t *testing.T) { +func TestApplyTransition_ReturnsHandlerError_ButSetsPhase(t *testing.T) { t.Parallel() state := &State{Phase: PhaseActive} handler := &mockActionHandler{returnErr: errors.New("condense failed")} - // Synthetic transition with [Condense, UpdateLastInteraction] actions. result := TransitionResult{ NewPhase: PhaseIdle, - Actions: []Action{ActionCondense, ActionUpdateLastInteraction}, + Actions: []Action{ActionCondense}, } err := ApplyTransition(context.Background(), state, result, handler) require.Error(t, err) assert.Contains(t, err.Error(), "condense failed") + // Phase must still be set even though handler failed. assert.Equal(t, PhaseIdle, state.Phase) - // Common action must still run even though handler failed. - require.NotNil(t, state.LastInteractionTime, - "UpdateLastInteraction must run despite earlier handler error") } func TestApplyTransition_StopsOnFirstHandlerError(t *testing.T) { From ab5a6befabe96e47c31b9fa2ce6a316ec30029f7 Mon Sep 17 00:00:00 2001 From: Stefan Haubold Date: Sun, 1 Mar 2026 21:56:34 +0100 Subject: [PATCH 2/3] PR feedback Entire-Checkpoint: bf9a80c410b2 --- cmd/entire/cli/session/phase_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cmd/entire/cli/session/phase_test.go b/cmd/entire/cli/session/phase_test.go index e96a6c779..ee821074f 100644 --- a/cmd/entire/cli/session/phase_test.go +++ b/cmd/entire/cli/session/phase_test.go @@ -551,6 +551,24 @@ func TestApplyTransition_StopsOnFirstHandlerError(t *testing.T) { assert.False(t, handler.warnStaleSessionCalled, "should stop on first error") } +func TestApplyTransition_UpdateLastInteractionRunsDespiteHandlerError(t *testing.T) { + t.Parallel() + + state := &State{Phase: PhaseEnded} + handler := &mockActionHandler{returnErr: errors.New("condense failed")} + result := TransitionResult{ + NewPhase: PhaseEnded, + Actions: []Action{ActionCondenseIfFilesTouched, ActionUpdateLastInteraction}, + } + + err := ApplyTransition(context.Background(), state, result, handler) + + require.Error(t, err) + assert.Contains(t, err.Error(), "condense failed") + assert.True(t, handler.condenseIfFilesTouchedCalled) + require.NotNil(t, state.LastInteractionTime, "UpdateLastInteraction must run despite earlier handler error") +} + func TestApplyTransition_ClearEndedAtRunsDespiteHandlerError(t *testing.T) { t.Parallel() From 2335da51b4856d7644117da33ffc1a8b19d34b93 Mon Sep 17 00:00:00 2001 From: Alex Ong Date: Mon, 2 Mar 2026 10:57:25 +1100 Subject: [PATCH 3/3] Update LastInteractionTime comment to reflect git hook exclusion Co-Authored-By: Claude Opus 4.6 Entire-Checkpoint: 8a8eb3b01307 --- cmd/entire/cli/session/state.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/entire/cli/session/state.go b/cmd/entire/cli/session/state.go index 1bfdfeadf..bed1f9e1e 100644 --- a/cmd/entire/cli/session/state.go +++ b/cmd/entire/cli/session/state.go @@ -84,7 +84,8 @@ type State struct { // - Cleared when session is reset (ResetSession deletes the state file entirely) TurnCheckpointIDs []string `json:"turn_checkpoint_ids,omitempty"` - // LastInteractionTime is updated on every hook invocation. + // LastInteractionTime is updated on agent-interaction events (TurnStart, + // TurnEnd, SessionStop, Compaction) but NOT on git commit hooks. // Used for stale session detection in "entire doctor". LastInteractionTime *time.Time `json:"last_interaction_time,omitempty"`