fix(sessions): two data-loss bugs around nested session names #66
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/session-worktree-enumeration"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found while reviewing #47. Both are pre-existing on
main, independent of that PR, and both destroy operator work. Nested session names (feat/login) are explicitly supported —validateGitRefNameallows them and AGENTS.md advertises them — and both bugs follow from such a worktree living two levels deep.1.
convergeSessionshard-deleted every nested session on restartlistWorktreesenumerated withls -1 /workspace/.nexus/worktrees— non-recursive. Verified live in a running worker:So for a row named
feat/codegraph, inconvergeSessions:worktreesOk === truebutworktreeNames.has('feat/codegraph') === falsewhich is exactly the "worktree positively confirmed gone" branch. The row was hard-deleted — along with its pinned
claude_session_id, i.e. the conversation — while the worktree sat untouched on disk. It even logsdeleting orphan row (worktree gone)with the worktree right there.The documented safety property ("uncertainty never triggers deletion", AGENTS.md fact #15) did not help:
lsexits 0, so absence looked confirmed rather than unknown.Fix: ask git, not the filesystem.
git worktree list --porcelainreports the full nested path.parseWorktreePorcelainandworktreeNamesFromPathsare pure and unit-tested.2.
createSession's recoveryrm -rfcould delete a live session's worktreeGit refs form a directory tree, so
featandfeat/logincannot coexist. Creatingfeatwhilefeat/loginis running:git worktree add(ref D/F conflict),rm -rf /workspace/.nexus/worktrees/feat—The retry then fails too, so the operator gets an error and a destroyed session.
Fix: reject the clash up front (
conflictingSessionName) with an error naming the conflicting session, so the destructive path is unreachable. Belt and braces: the recoveryrm -rfnow refuses a directory that still contains a registered worktree (a worktree can outlive its row), andworktree pruneruns before the retry so a stale registration can't wedge the add forever.Why the suite missed this
The fake docker returned the worktree names verbatim, modelling
ls -1as if it were recursive. The mock was more capable than the real command, so the bug was invisible to a green suite. It now emits genuine porcelain — which is what makes the new regression test meaningful.Tests
restores a nested-name session instead of deleting it— the regression.still deletes a nested-name row when the worktree really is gone— the guard didn't get blunted.parseWorktreePorcelain/worktreeNamesFromPaths(incl. ignoring/workspaceand non-session worktrees).conflictingSessionName, including thatfeatureandfeat-logindo not conflict withfeat/login(segment-wise, not string-prefix).pnpm test572/572 ·svelte-check0 errors ·eslintclean.Note
deleteSessionandremoveWorkerare unaffected — they remove a specific path they own. The nested-name D/F rule also means no session worktree can ever be an ancestor of another, which is the property the codegraph work in #47 depends on.