Cleanup can make uncondensed checkpoint commits unreachable after state expiry or a ref read failure
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 5.1k
- Forks
- 475
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 178
Description
What happened?
Entire keeps uncondensed checkpoint commits reachable through shadow branches. Cleanup uses session state to decide whether a shadow branch still contains work that must be preserved.
Two independent bugs can delete valid session state before condensation finishes:
- Loading session state deletes it once it is more than seven days old, even when it records pending checkpoint work.
- Session discovery deletes state when a Git ref lookup returns
plumbing.ErrReferenceNotFound, even when the ref exists but could not be read.
Both bugs produce the same failure:
- A session has checkpoint commits that exist only on its shadow branch.
- Entire deletes the session state that associates those commits with pending work.
- Cleanup classifies the shadow branch as unused and deletes it.
- The shadow-only commits become unreachable.
- Git may eventually garbage-collect them.
This affects checkpoint history that has not yet been condensed. It does not delete working-tree files or commits already reachable from ordinary Git branches.
Bug 1: state expiry ignores pending work
StateStore.Load deletes session state after seven days based only on its timestamp.
It does not check whether the state still records uncondensed work. A valid state with FullyCondensed: false and a non-zero StepCount is therefore deleted as soon as it crosses the expiry threshold.
The expiry check is destructive. Merely loading the state removes the evidence cleanup needs to protect the corresponding shadow branch.
This affects both active and ended sessions.
Bug 2: a ref read failure is treated as proof that the ref is absent
Session discovery removes state that appears to refer to a missing shadow branch.
An existing loose ref can be unreadable because of a filesystem error or permission problem. In that case, go-git may return plumbing.ErrReferenceNotFound. Session discovery treats that result as proof that the ref does not exist and deletes the state as orphaned.
If the ref becomes readable again, its session state is already gone. The next cleanup pass sees no session protecting the branch and deletes it.
A failed lookup is not enough evidence that deleting the state is safe.
Steps to reproduce
State expiry
- Create a shadow branch containing a commit that is not reachable from any ordinary branch.
- Save valid session state for the shadow branch with
FullyCondensed: falseandStepCount: 1. - Set the session timestamps to one hour ago.
- Run pushed-shadow cleanup.
- Confirm that cleanup preserves the shadow branch.
- Change only the timestamps so the state is eight days old.
- Load the state or run the cleanup path that loads it.
- Observe that Entire deletes both the state and the shadow branch.
- Run
git fsck --unreachable --no-reflogs. - Observe that the shadow-only commit is unreachable.
Observed output:
phase=active control_deleted=0 aged_deleted=1 state_deleted=true
phase=ended control_deleted=0 aged_deleted=1 state_deleted=true
Changing only the age of the state changes the result from protected to deleted, even though its pending checkpoint work is identical.
Unreadable Git ref
- Create an idle session with pending work and a shadow branch.
- Confirm that session discovery returns the session.
- Remove read permission from the loose ref.
- Confirm that the ref lookup returns
reference not found. - Run session discovery again.
- Observe that discovery deletes the valid session state as orphaned.
- Restore read permission on the ref.
- Run cleanup.
- Observe that cleanup deletes the now-unprotected shadow branch.
- Run
git fsck --unreachable --no-reflogs. - Observe that the shadow-only commit is unreachable.
Observed output:
ref_error=reference not found list_error=<nil> state_deleted=true
This reproduction requires a Unix filesystem that enforces file permission changes.
Expected behavior
- State containing uncondensed checkpoint work must not expire based on age alone.
- Session discovery must delete orphaned state only after it can establish that the corresponding ref is absent.
- An ambiguous or failed ref read must preserve the state or return an error.
- Cleanup must preserve a shadow branch whenever it cannot prove that no session needs it.
- Shadow-only commits must remain reachable until their checkpoint history has been condensed.
Entire CLI version
Entire CLI 0.10.5-nightly.202609030626.408372c97
Go version: go1.26.6
The bugs were reproduced against source revision 500140839.
OS and architecture
Darwin 25.6.0 arm64
Additional context
#2350 covers malformed session JSON being omitted from cleanup inventory. PR #2358 addresses that case.
The bugs described here have different triggers:
- The expiry case begins with valid, readable state that still records pending work.
- The ref case begins with valid state and an existing ref, but a read failure causes discovery to discard the state.
The investigation and fix are recorded on Entire trail 3, using branch fix-session-cleanup-safety.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with StateStore.Load and the session-discovery path, then trace how cleanup decides whether a shadow branch is protected. Reproduce both the seven-day expiry and unreadable-ref cases described in the issue. Done means pending work remains protected, failed ref reads do not discard state, and shadow-only commits stay reachable; verify with git fsck --unreachable --no-reflogs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, go
- Domain
- cli, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100