fix(status): list every session without mutating state - #2363
Open
peyton-alt wants to merge 3 commits into
Open
Conversation
`entire status` reports what sessions exist, and reporting should not change what it reports. Today status goes through List, which deletes any record that has gone stale — so asking the question mutates the answer. Split the stale cleanup behind a parameter and expose ListReadOnly for the passive path. List and Load keep their existing contract: doctor and the session sweeper own cleanup and are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M27BBHCNZ7H36WTWRFT6TQB1
Two defects in how status reports sessions, both fixed by the same change. `--json` collapsed active_sessions to one entry per agent, so a developer running two sessions for the same agent — one per worktree, a normal setup — saw only one of them, and there was no way to recover the other from the output. Each session is now its own entry, carrying session_id. Consumers wanting a per-agent view can group on the agent field; they could not do the reverse. Both the text and JSON paths also read through List, which deletes stale records as a side effect, and ran finalizeExitedSessions. So looking at status changed what status reported. Both now read through ListReadOnly and finalize nothing. Cleanup is unchanged for the callers that own it: doctor and the session sweeper still call List and still finalize. Replaces TestRunStatusJSON_DeduplicatesSessions, which pinned the old shape, with a test asserting each session is reported separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M27BPPKF4WMPH5FJRK1ZV126
Ports the two integration regressions: two sessions for one agent across worktrees both appear with their worktree_path, and a dead-owner session is reported as exited with its state file and transcript byte- and mtime-identical afterwards. Adds worktree_path and branch to the per-session JSON entries — without them two entries for the same agent are reported but not distinguishable, which was the point. CLAUDE.md and sessions-and-checkpoints.md both stated that finalizeExitedSessions sweeps inside `entire status`. That is no longer true and is now recorded as a deliberate property: doctor and the sweeper own cleanup, status observes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M27C71EV3V1V1MQYXE4ZQ6S1
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
Unresolved moderate findings remain in status behavior and integration coverage.
Pull request overview
Updates entire status to read session state without mutation and report every session separately in JSON.
Changes:
- Adds read-only session listing while retaining cleanup for mutating callers.
- Adds per-session JSON metadata and removes status-time finalization.
- Expands tests and updates lifecycle documentation.
File summaries
| File | Reviewed changes |
|---|---|
docs/architecture/sessions-and-checkpoints.md |
Documents read-only status behavior; nit remains to update contradictory finalization comments. |
cmd/entire/cli/status.go |
Adds read-only reporting and per-session JSON output; moderate findings remain around branch-resolution caching, stale status labeling, and branch assertions. |
cmd/entire/cli/status_test.go |
Covers per-session JSON output and read-only behavior. |
cmd/entire/cli/session/state.go |
Adds ListReadOnly; clarify that other List callers retain cleanup behavior. |
cmd/entire/cli/session/state_test.go |
Tests preservation of stale records during read-only listing. |
cmd/entire/cli/integration_test/status_health_test.go |
Tests session visibility and immutability; moderate findings remain around platform-aware liveness assertions and branch validation. |
CLAUDE.md |
Updates session lifecycle guidance. |
Review details
Suppressed comments (7)
cmd/entire/cli/integration_test/status_health_test.go:59
- This fixture assumes
proclive.Checkcan prove that the recorded owner is dead. On platforms where process introspection is unsupported (for example Windows),CheckreturnsLivenessUnknown; becauselastActiveis only one minute old,sessionStatusLabelfalls back toactive, so the assertions forexitedfail. Make the test platform-aware (or assert the platform's time-based fallback) while retaining the byte/mtime checks.
Owner: &proclive.Identity{PID: os.Getpid(), Start: "not-this-process"}, TranscriptPath: transcriptPath,
cmd/entire/cli/integration_test/status_health_test.go:39
- The new
branchfield is not decoded or asserted here, so the integration test would still pass ifstatus --jsonomitted it or returned the wrong branch. Since this field is part of the new per-session shape, include it in the fixture and assert the two worktree branches as well.
ActiveSessions []struct {
SessionID string `json:"session_id"`
WorktreePath string `json:"worktree_path"`
} `json:"active_sessions"`
cmd/entire/cli/session/state.go:1111
- This says cleanup stays with only doctor and the sweeper, but
Listis also reached throughstrategy.ListSessionStatesby commands such as session stop/resume, cleanup, and caller resolution. Clarify that status opts out while other callers that useListretain its cleanup contract.
// `entire status` is the caller this exists for: asking what is happening must
// not change what is happening. Cleanup stays with the callers that own it —
// doctor and the session sweeper — which keep using List.
cmd/entire/cli/status.go:976
- This fallback runs inside the per-session loop, so every legacy or detached session with an empty
Branchresolves the same worktree independently.resolveWorktreeBranchperforms filesystem reads and shells out togitfor reftable stubs (with a 5-second timeout), so N sessions in one worktree can turn status into N repeated resolutions. Cache the resolved branch byWorktreePathfor this status snapshot before appending the entries.
branch := st.Branch
if branch == "" && st.WorktreePath != "" {
branch = resolveWorktreeBranch(ctx, st.WorktreePath)
cmd/entire/cli/status.go:985
- Now that
ListReadOnlyreturns stale ACTIVE records, this can emit them withstatus:"active":sessionStatusLabelhas noIsStuckActivebranch, while the text path renders those same states asstale. Preserve that health distinction in JSON (or filter stale records) and add a regression test.
Status: sessionStatusLabel(st),
CaptureDegraded: st.CaptureDegradedAt != nil,
cmd/entire/cli/status.go:978
Branchis newly emitted for every JSON session, including theresolveWorktreeBranchfallback, but the added tests only assert session IDs and worktree paths. A regression could therefore return an empty or wrong branch without failing; please assert both persisted branches and the fallback case.
branch := st.Branch
if branch == "" && st.WorktreePath != "" {
branch = resolveWorktreeBranch(ctx, st.WorktreePath)
}
result.ActiveSessions = append(result.ActiveSessions, sessionBriefJSON{
docs/architecture/sessions-and-checkpoints.md:358
- This documentation change leaves the authoritative comments in
cmd/entire/cli/session_finalize.go:22-23and:47-48saying thatentire statusinvokes the exited-session sweep. Status no longer callsfinalizeExitedSessions, so those comments now describe a nonexistent caller and contradict this update; please revise them together with the architecture docs.
sweep (`finalizeExitedSessions`, run inside `entire doctor` and the session sweeper — not `entire status`, which is read-only), which
- Files reviewed: 7/7 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This was referenced Sep 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
https://entire.io/gh/entireio/cli/trails/1299
Problem
entire statushad two defects in how it reports sessions, both fixed here.--jsoncollapsedactive_sessionsto one entry per agent. Two sessions for the same agent — one per worktree, a normal setup — showed as one, and nothing in the output let a consumer recover the second. The text path already listed every session, so the two surfaces disagreed.Reporting mutated what it reported. Both paths read through
StateStore.List, which deletes stale records as it reads, and both ranfinalizeExitedSessions. Running the command people run to look at state changed that state: a stale session vanished because you looked at it, and a dead-owner session was finalized on read instead of being reported as exited.Summary
StateStore.ListReadOnlybeside the existing mutating reads, splitting stale cleanup behind a parameter--jsonreports one entry per session, carryingsession_id,worktree_path, andbranchListandLoadkeep their cleanup contract exactly as before —entire doctorand the session sweeper still call them and still finalize, verified by test.Behaviour changes worth calling out in review
active_sessionsJSON shape: one entry per session instead of one per agent. Consumers grouping onagentstill work; anything relying on exactly one entry per agent does not. The reverse was impossible — a collapsed entry could not be un-collapsed.exiteduntildoctoror the sweeper reaches it.CLAUDE.mdandsessions-and-checkpoints.mdboth documented the old behaviour and are corrected here.TestRunStatusJSON_DeduplicatesSessionspinned the old shape and is replaced byTestRunStatusJSON_ReportsEachSessionSeparatelyrather than deleted.Verification
mise run check— unit, integration, and the Vogon canary, all greenTestStateStore_ListReadOnly_ReturnsStaleWithoutDeletingasserts stale records survive a read and thatListstill deletes themTestRunStatusJSON_ListsEverySessionWithoutMutating— went red on both defects before the fixTestStatusMultipleSessionsPreservedAcrossWorktrees,TestStatusReadOnlyPreservesDeadOwnerStateAndTranscript(asserts state file and transcript are byte- and mtime-identical after status)Fixes #2362
Split out of #2343, which bundled this with unrelated Git-hook work.
🤖 Generated with Claude Code
Note
Medium Risk
Changes observable
status --jsonshape (breaking for consumers that assumed one entry per agent) and defers finalization of exited sessions until doctor/sweeper, which may leave sessions visible longer but avoids side effects on a read-only command.Overview
entire statusis now read-only for session state. Both text and JSON paths useStateStore.ListReadOnlyinstead ofList, so stale session files are not deleted on read andfinalizeExitedSessionsis no longer run from status. Dead-owner sessions show asexiteduntilentire doctoror__sweep_sessionsfinalizes them; docs (CLAUDE.md,sessions-and-checkpoints.md) match that split.status --jsonlists one row per session, not one per agent.active_sessionsentries addsession_id,worktree_path, andbranchso multiple concurrent sessions for the same agent (e.g. per worktree) are all visible and align with the text output.List/Loadstill delete stale sessions whendeleteStaleis true; doctor and the sweeper keep usingList. New unit and integration tests cover read-only listing, JSON shape, and no mutation of state or transcript after status.Reviewed by Cursor Bugbot for commit b5c900d. Configure here.