Skip to content
This repository was archived by the owner on Nov 5, 2026. It is now read-only.

Never let a reconcile empty a live sandbox slice - #62

Merged
ndr-ds merged 2 commits into
mainfrom
ndr-ds/never-empty-a-live-slice
Aug 21, 2026
Merged

ndr-ds merged 2 commits into
mainfrom
ndr-ds/never-empty-a-live-slice

Conversation

@ndr-ds

@ndr-ds ndr-ds commented Aug 21, 2026

Copy link
Copy Markdown

Problem

Sandbox session slices in sandbox-sessions.json have been silently lost more than once. Today 62 sessions vanished from the TUI at once, leaving two registered sandboxes with no key in the registry at all.

Two very different callers share one wholesale writer:

  • forget_session — a deliberate close. Emptying the slice is a decision.
  • the reconcile hook (hook_state.rs:389) — passes whatever a live scan returned. Emptying the slice is a measurement.

A scan returns empty for reasons that are not "every session ended": the process-table read failed, the pointer files were already gone, the hook fired during teardown. replace_sandbox_slice could not tell those apart from a real close, so one bad reading deleted the key — and the key is what --restore-sbx-sessions reads, so the sessions became unrecoverable through the normal path.

The module already defends against a degraded reading — backfill_missing_identity restores session titles a partial scan dropped. It cannot help here, because a fully empty reading leaves no entry to backfill into.

Change

EmptySlice::{Freeze, Remove} makes the caller state which kind of empty it means.

  • replace_sandbox_slice defaults to Freeze: an empty reading against a non-empty slice is a no-op.
  • forget_session opts into Remove, so a deliberate close of the last session still drops the key.

The safe behaviour is the default, so a future caller has to ask for deletion rather than get it by accident.

Tests

  • a_reconcile_that_measures_zero_never_empties_a_live_slice — new, covers the failure directly.
  • a_deliberate_close_of_the_last_session_still_removes_the_sandbox — the old replace_sandbox_slice_empty_removes_the_sandbox, retargeted at the explicit path it was really describing.
  • replace_sandbox_slice_keeps_sandboxes_independent used empty-removal incidentally while testing independence; switched to the explicit path so it keeps testing what it names.

cargo test --lib sandbox_registry 42 passed, session_lifecycle_tests 7 passed, cargo clippy --all-targets -- -D warnings clean.

Recovery for the sessions already lost

Not part of this change, but for the record: the 62 sessions were rebuilt into the registry from ~/.local/share/claudectl/state/*.json (entries whose last_session_end_ts_ms is 0 never emitted SessionEnd) cross-referenced against surviving transcripts under ~/.claude/projects/. restore_slice only needs session_id and cwd, so the missing pids were not a blocker.

@ndr-ds
ndr-ds marked this pull request as ready for review August 21, 2026 15:39
@ndr-ds
ndr-ds merged commit a4b49f4 into main Aug 21, 2026
5 checks passed
@ndr-ds
ndr-ds deleted the ndr-ds/never-empty-a-live-slice branch August 21, 2026 15:39
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant