strategy: capture session owner at each turn start · Entire
strategy: capture session owner at each turn start
b91e180·
Soph·3w ago·2 files·+57 added/-0 removed
InitializeSession records the owning agent process via proclive.ResolveOwner alongside captureSessionBranch, on every turn start. The field is cleared first so a failed resolve never leaves a stale (possibly dead) owner from an earlier turn that would wrongly finalize a now-live session; re-resolving each turn also keeps the fingerprint current across agent restarts.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Sessions
75d62535acd8View transcript
[?
Fix Session Owner Tracking and FinalizationClaude Code·Opus 4.8[1m]·5 steps](/content/gh/entireio/cli/session/2a8a70c0-e3c4-47ef-ba38-493022455e34#timeline-75d62535acd8/index.html)
Changes
2
cmd/entire/cli/strategy
Mmanual_commit_hooks.go+23
Aowner_wiring_test.go+34
24 unmodified lines
25
26
27
28
29
30
31
2265 unmodified lines
2297
2298
2299
2300
2301
2302
2303
38 unmodified lines
2342
2343
2344
2345
2346
2347
2348
24 unmodified lines
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2390
2391
2392
2393
2394
2395
2396
2397
2398
24 unmodified lines
"github.com/entireio/cli/cmd/entire/cli/interactive"
"github.com/entireio/cli/cmd/entire/cli/logging"
"github.com/entireio/cli/cmd/entire/cli/paths"
"github.com/entireio/cli/cmd/entire/cli/proclive"
"github.com/entireio/cli/cmd/entire/cli/session"
"github.com/entireio/cli/cmd/entire/cli/settings"
"github.com/entireio/cli/cmd/entire/cli/stringutil"
2265 unmodified lines
state.TranscriptPath = transcriptPath
}
captureSessionBranch(repo, state)
captureSessionOwner(state)
// ORDERING: attribution runs BEFORE migrate to use the pre-migration
// BaseCommit as the base tree (preserving correct agent-line counts
38 unmodified lines
promptAttr := s.calculatePromptAttributionAtStart(ctx, repo, state)
state.PendingPromptAttribution = &promptAttr
captureSessionBranch(repo, state)
captureSessionOwner(state)
return nil
})
if mutErr != nil && !errors.Is(mutErr, ErrStateNotFound) {
24 unmodified lines
}
}
// captureSessionOwner records the owning agent process (PID + start-time
// fingerprint) into the session state so liveness checks can later detect an
// ACTIVE session whose agent exited without firing a SessionStop hook. The hook
// runs as a short-lived child of the agent, so proclive.ResolveOwner walks up
// past our own binary and any shells to the long-lived agent process.
//
// It is best-effort and re-run on every turn start: if the owner can't be
// resolved (unsupported platform, transient-only ancestry) the field is cleared
// and liveness degrades to the inactivity timeout; re-resolving each turn keeps
// the fingerprint current across agent restarts.
func captureSessionOwner(state *SessionState) {
// Clear first: a failed resolve must never leave a stale owner from an
// earlier turn. Otherwise a now-live session (agent restarted with a new
// PID) could still carry a dead PID and be wrongly finalized as exited.
state.Owner = nil
if owner, ok := proclive.ResolveOwner(); ok {
state.Owner = &owner
}
}
// calculatePromptAttributionAtStart calculates attribution at prompt start (before agent runs).
// This captures user changes since the last checkpoint - no filtering needed since
// the agent hasn't made any changes yet.
Mcmd/entire/cli/strategy/manual_commit_hooks.go+23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//go:build linux || darwin
package strategy
import (
"context"
"testing"
"github.com/entireio/cli/cmd/entire/cli/proclive"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestInitializeSession_CapturesOwner verifies that a turn start records the
// owning process identity, and that the live owner reads as not-exited.
func TestInitializeSession_CapturesOwner(t *testing.T) {
if _, ok := proclive.ResolveOwner(); !ok {
t.Skip("no stable process owner resolvable in this environment")
}
dir := setupGitRepo(t)
t.Chdir(dir)
s := &ManualCommitStrategy{}
err := s.InitializeSession(context.Background(), "test-session-owner", "Claude Code", "", "", "")
require.NoError(t, err)
state, err := s.loadSessionState(context.Background(), "test-session-owner")
require.NoError(t, err)
require.NotNil(t, state.Owner, "InitializeSession should capture the owning process")
assert.Positive(t, state.Owner.PID, "captured owner PID should be positive")
assert.False(t, state.OwnerExited(), "a freshly-captured live owner must not read as exited")
}
Acmd/entire/cli/strategy/owner_wiring_test.go+34