Skip to content

resurrect + continuum persist unnamed sessions, recreating them on every server start #37

Closed

Description

@CodedRichy

Repo: psmux/psmux-plugins (psmux-resurrect, psmux-continuum)
psmux: 3.3.8 (66cf613, 2026-08-18), installed via winget
Windows: 11 Pro 26200 · PowerShell 7.6.5 and Windows PowerShell 5.1

Summary

psmux-resurrect saves every live session including unnamed ones — the 0, 1, 2
that psmux assigns when you run a bare psmux/tmux. With
@continuum-restore 'on', those get recreated on every server start, so a session
you never deliberately made becomes permanent.

The part that makes it more than cosmetic is that it cannot be cleaned up by hand.
Deleting session 0 from the save files fixes nothing: as soon as a stray 0 exists
again for any reason, the next auto-save writes it straight back and every subsequent
restore recreates it. Save → restore → save is a closed loop with no exit.

Practical effect: you type tmux, land in session 0, and your real workspace is
restored beside you rather than under you. Every session, forever.

Reproduction

# 1. Start from a clean server
tmux.exe kill-server

# 2. Bare invocation -> psmux names the session "0"
tmux.exe                        # (detach)

# 3. Let continuum's auto-save run, or force it
pwsh -NoProfile -File "$env:USERPROFILE\.psmux\plugins\psmux-resurrect\scripts\save.ps1"

# 4. The save now contains session "0"
$last = (Get-Content "$env:USERPROFILE\.psmux\resurrect\last" -Raw).Trim()
(Get-Content $last -Raw | ConvertFrom-Json).sessions.name
# -> 0
#    <your real sessions>

# 5. Remove "0" from every save file by hand, then restart the server.
#    It comes back the moment anything creates an unnamed session again.

Observed here across a working day: next_session_id reached 48, and the process
tree confirms restore is the thing recreating it —

psmux.exe server -s 0 -d C:\Users\rishi -n pwsh          <- recreated by restore.ps1
psmux.exe server -s claude-code -d C:\Users\rishi -n claude

Both spawned by restore.ps1, identical shape, from a save whose 0 entry I had
already deleted once.

Root cause

psmux-resurrect/scripts/save.ps1:57 enumerates every session with no filter:

$sessionLines = (& $PSMUX list-sessions -F '#{session_name}' 2>&1) | Out-String
...
foreach ($line in ($sessionLines -split "`n")) {
    $sessionName = $line.Trim()
    if ([string]::IsNullOrWhiteSpace($sessionName)) { continue }
    # no check for auto-assigned names

An unnamed session carries no user intent — the name is an implementation detail of
how psmux numbers sessions. tmux-resurrect has the same behaviour upstream, but it
bites harder here because psmux spawns internal helper sessions (__warm__) and a
bare psmux is a much more common entry point on Windows, where users reach for the
plain command rather than a shell alias.

Suggested fix

Skip auto-named sessions at save time, behind an option so anyone relying on the
current behaviour can opt back in:

# save.ps1, inside the foreach at :57
if ([string]::IsNullOrWhiteSpace($sessionName)) { continue }

# Auto-assigned names carry no user intent, and persisting them makes them
# permanent: restore recreates them, the next save re-persists them.
if ($saveUnnamed -ne 'on' -and $sessionName -match '^\d+$') { continue }

with $saveUnnamed read once before the loop:

$saveUnnamed = (& $PSMUX show-options -gv '@resurrect-save-unnamed' 2>&1 | Out-String).Trim()

Filtering at save time rather than restore time is the important part — restore-side
filtering still leaves the poisoned record on disk for anything else that reads it.

Internal sessions (__warm__) are worth excluding on the same pass.

Workaround, for anyone who finds this first

Wrap save.ps1 and strip unnamed sessions from the result, then point the hooks at the
wrapper instead of the plugin script. Note that @continuum-save-interval appears to be
non-functional — auto_save.ps1 only reads -IntervalMinutes as a param() and never
looks the option up — so changing cadence means overriding the client-attached hook,
and that override must sit below the run '~/.psmux/plugins/ppm/ppm.ps1' line in
psmux.conf or ppm clobbers it when it sources each plugin's own plugin.conf.

Happy to open a PR for the save-side filter if the approach looks right.

Activity

  1. psmux commented on Sep 14, 2026

    @psmux
    Owner

    Hey @CodedRichy, thank you for this one. The root cause section was exactly right and the closed loop you described (save, restore, save) is what makes it worse than cosmetic, so I reproduced it end to end before touching anything.

    Reproduction on 3.3.8 (7ffcb3d), isolated data root

    1. new-session -d with no name (what a bare psmux does) plus a named work
    2. save.ps1 wrote both: SAVED SESSION NAMES: [0, work]
    3. kill-server, then restore.ps1: Restored session: 0 (1 windows)
    4. save.ps1 again: 0 is back in the save

    So the loop is real and it only takes one bare launch to seed it.

    What landed (01c03ab, on main now)

    The filter lives at save time, as you argued, behind @resurrect-save-unnamed:

    set -g @resurrect-save-unnamed 'auto'   # default
    • auto (default): an auto-named session is saved only once you have shaped it, meaning a second window or pane, or a program other than an idle shell in its pane. An untouched 0 is what the next bare launch gives you anyway, so it is left out.
    • on: save every auto-named session, which is what tmux-resurrect does and what the plugin did until today.
    • off: never save auto-named sessions, your proposal as written.

    I went with auto rather than off as the default because a lot of people do real work in session 0 (bare tmux is the most common entry point there too), and silently dropping a session with six windows because of its name would trade your bug for theirs. The untouched shape is the one that carries no intent, and it is the shape every stray 0 in your process tree had.

    Sessions whose names start with __ (the __warm__ standby) are never saved regardless of the setting. When every running session was filtered out the save now says so explicitly and does not repoint last, instead of the old "server likely down" line. The save toast counts what was skipped.

    Verified the same way: the untouched 0 stays out of the save, a split 1 and named sessions still round trip, a cold restore brings back 1 and work only, and the save taken right after the cold restore is still clean. All of that is pinned in tests/test_resurrect_save_unnamed.ps1 against the real binary in a private PSMUX_DATA_DIR.

    Your @continuum-save-interval side note was also correct

    The option was documented but the hook runs auto_save.ps1 -IntervalMinutes 15 and the script never read it. I confirmed that the hook's exact invocation with the option set to 1 produced no save in 80 seconds. baab0f9 makes the loop read @continuum-save-interval at the top of every lap: a number wins over the parameter, 0 stops the loop, and a change made on a running server takes effect without restarting anything. Same 80 second probe after the fix: one save written. So you no longer need to override the hook, and the ppm ordering trick is not needed for cadence any more. (Sourcing order is otherwise as you described and intentional: ppm applies each plugin's plugin.conf, and anything you want to override has to come after the ppm line.)

    One thing worth knowing while you test: psmux runs one server process per session, so a set -g typed at runtime only reaches the session it targets. Put the options in ~/.psmux.conf and they apply to every session as it starts. I added that note to the README as well.

    Prefix + U in ppm pulls the update. If a stray 0 still comes back after that, please tell me what psmux ls and the save file show and I will dig in again.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions