Skip to content

fix(resurrect): skip empty-session snapshots and preserve last pointer#27

Merged
psmux merged 1 commit into
psmux:mainfrom
TrvoDK:fix/resurrect-skip-empty-snapshot
Jul 17, 2026
Merged

fix(resurrect): skip empty-session snapshots and preserve last pointer#27
psmux merged 1 commit into
psmux:mainfrom
TrvoDK:fix/resurrect-skip-empty-snapshot

Conversation

@TrvoDK

@TrvoDK TrvoDK commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Part of #26.

save.ps1 could persist a 0-session snapshot and repoint ~/.psmux/resurrect/last to it. When no server is running, list-sessions returns empty (psmux exits 0 with no output), so the capture is empty — but the snapshot was still written and last repointed, and the 20-slot rotation then evicted the good snapshots, silently destroying the ability to resurrect.

This adds a guard after the session-capture loop: if 0 sessions were captured, skip the write entirely and leave last untouched. A 0-session capture means the server is down (psmux/tmux destroys the server when its last session is gone), so an empty snapshot is never worth persisting — and persisting it is actively harmful. The guard sits before the dedup/write/rotation.

It protects every caller of save.ps1 (the continuum auto-save loop, the client-detached save hook, and the manual Prefix+Ctrl-s binding), so the invariant "never persist a meaningless snapshot" lives with the writer rather than being re-implemented per caller.

Known limitation (follow-up, not fixed here): a partial capture — a server mid-startup reporting some-but-not-all sessions — is not covered; that snapshot has ≥1 session so it passes this guard and is written. Closing that needs an expected-session-count signal the script doesn't have today.

Tests: tests/test_resurrect_guard.ps1 — hermetic (fake-psmux shim resolved via PATHEXT + redirected $env:USERPROFILE; needs no server and no real psmux binary, unlike the existing test_resurrect*.ps1 which wipe the real ~/.psmux/resurrect). Asserts: an empty capture writes nothing and leaves last byte-identical (both with a prior good last and on a virgin dir, so the guard is exercised independently of the dedup); a live session still writes (no false skip); and a characterization test pinning the known partial-capture gap.

Validation: there is no CI in this repo and the live E2E harness is destructive against the real resurrect dir, so this was validated via the hermetic sandbox test above + code reasoning — not a CI run.

save.ps1 could persist a 0-session snapshot and repoint
~/.psmux/resurrect/last to it. When no server is running, list-sessions
returns empty (psmux exits 0 with no output), so the capture was empty --
but the snapshot was still written and last repointed, and the 20-slot
rotation then evicted the good snapshots, silently destroying the ability
to resurrect.

Add a guard after the capture loop: if 0 sessions were captured, skip the
write entirely and leave last untouched. A 0-session capture means the
server is down (psmux/tmux destroys the server when its last session is
gone), so an empty snapshot is never worth persisting. The guard sits
before the dedup/write/rotation and protects every caller of save.ps1
(the continuum auto-save loop, the client-detached hook, and the manual
Prefix+Ctrl-s binding).

Known limitation (follow-up): a partial capture -- a server mid-startup
reporting some-but-not-all sessions -- has >=1 session so it passes this
guard and is still written. Closing that needs an expected-session-count
signal the script does not have today.

Tests: tests/test_resurrect_guard.ps1 -- hermetic (fake-psmux shim +
redirected USERPROFILE; needs no server and no real binary, unlike the
existing test_resurrect*.ps1 which wipe the real resurrect dir).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@psmux

psmux commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Hey @TrvoDK, thanks for chasing this one down properly. I reproduced #26 hermetically before even opening this diff (sandboxed USERPROFILE, a fake psmux.ps1 shim on PATH via PATHEXT, seeded a good multi window snapshot with last pointing at it, then ran save.ps1 against the shim simulating a downed server). On main, exactly as you described: save.ps1 wrote a fresh "sessions": [] snapshot and repointed last at it, so the good snapshot was orphaned and would eventually rotate out of the 20 slot history.

Then I checked out this branch and reran the exact same scenario against your patched save.ps1. It printed "No sessions captured, skipping save (server likely down)." and left both the snapshot directory and last byte identical to before the run. I also ran tests/test_resurrect_guard.ps1 as is, all 7 checks passed, including the two characterization checks you added for the partial capture gap.

The placement of the guard (after the capture loop, before dedup and before the last repoint) is exactly right since it means every caller inherits the protection for free rather than needing its own check. I also appreciate that you called out the partial capture case explicitly instead of quietly leaving it uncovered. That is a real and separate problem and deserves its own PR with its own signal for "how many sessions do we expect".

Merging this now as the keystone fix for #26. Great work, thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants