From 5036c4fae7188a56a20932bf1cd74b71288393c1 Mon Sep 17 00:00:00 2001 From: Lexus2016 Date: Sat, 3 Oct 2026 13:00:42 +0200 Subject: [PATCH 1/2] fix(sessions): expiry goes through the one session teardown, and stops leaking worktrees SESSION_TTL_DAYS ran a bare `DELETE FROM sessions`. Every expired session's git worktree and ccs/session-* branch stayed in data/worktrees with no row left to own it, and its queued_messages (no FK) were restored at every boot. Reported by a third-party Docker image that had to prune the trees in its own entrypoint. The three doors that delete sessions (DELETE, bulk delete, expiry) each carried their own copy, and they had drifted: the single delete never killed the terminal pane either. teardownSessions(rows, { deleteTasks }) is now the only code that deletes a session; sessionHasUnmergedWork() is the one gate the doors ask first. Expiry is unattended, so it skips a running session, a live terminal pane and a worktree holding unmerged or uncommitted work (an unreadable tree counts as work), and keeps the chat's Kanban cards (deleteTasks: false; the FK unlinks them). WM.pruneOrphanWorktrees() reconciles the trees earlier versions leaked: only trees no sessions/tasks/task_chains row owns, older than an hour, clean and merged, and removed without --force. It checks for data/worktrees before probing git, so an install with no worktrees spawns nothing at boot. test/session-expiry.test.js boots a real server twice against one APP_DIR and ages sessions in SQLite between the boots; reverting expiry to the bare DELETE fails 6 of its 17 checks. --- CLAUDE.md | 34 ++++- package.json | 2 +- server.js | 263 +++++++++++++++++++-------------- test/queue-persistence.test.js | 14 +- test/session-expiry.test.js | 163 ++++++++++++++++++++ test/worktree-manager.test.js | 73 +++++++-- worktree-manager.js | 57 ++++++- 7 files changed, 470 insertions(+), 136 deletions(-) create mode 100644 test/session-expiry.test.js diff --git a/CLAUDE.md b/CLAUDE.md index e48cd05d..8a7c6632 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,7 +31,7 @@ npm run release # bump, commit, tag, push → GitHub npm run release:mac # on the Mac with the cert: build, notarize, verify, upload, bump the cask ``` -No linting and no build step configured. `npm test` chains 87 test files under `test/`: 19 DOM-less render/UI-logic tests (`test/render/*.test.mjs`, run through `node --test`) plus 68 plain-`node` suites in `test/` covering the overload detector, env load order, multi-agent results, terminals, bots, telegram, updates, kanban scheduling, the Kanban card's run-settings badges (`kanban-run-badges.test.js` pins the card's model/effort/engine chain against the one `startTask` actually resolves — the two live in different files and the card silently lies when they drift), the board-only `create_task` status (`task-backlog.test.js`), the external agents' model/effort catalogs (`agent-catalog.test.js` — #123: the Settings form and `POST /api/external-agents` both carry them, and the browser's comma parser is RUN against the server sanitiser so the two cannot drift), the Kanban group form (`kanban-group-button.test.js` RUNS the real `buildChainForm` in a `vm` context so a stray free variable in its template throws there instead of silently killing the modal — see #115 — and parses every inline `