From 0064654d8f804b2b1892e5bb05edada315846249 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 18 Aug 2026 23:44:26 +0900 Subject: [PATCH] fix(scripts): recover group folder for already-unregistered rooms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a room was unregistered first (room_settings row already gone), the folder was unknown, so sessions rows and the on-disk group/workspace/session/ ipc folders (and any git worktree) were silently skipped — leaving orphans. Back-trace the folder from the group_folder recorded on the chat's leftover paired_tasks/work_items/scheduled_tasks/service_handoffs/paired_projects rows so folder-scoped cleanup still runs. Targets now carry a folder list. Co-Authored-By: Claude Opus 4.7 --- scripts/deregister-room.ts | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/scripts/deregister-room.ts b/scripts/deregister-room.ts index 0729930..1e875c1 100644 --- a/scripts/deregister-room.ts +++ b/scripts/deregister-room.ts @@ -60,21 +60,45 @@ const db = new Database(path.join(STORE_DIR, 'messages.db')); interface Target { jid: string; - folder: string | null; + folders: string[]; isMain: boolean; name: string | null; registered: boolean; } +// When a room was already unregistered (room_settings gone) the group folder is +// no longer stored there, so recover it from the group_folder recorded on the +// chat's leftover managed rows — otherwise sessions/disk/worktrees for an +// already-unregistered room would be missed. +function backtraceFolders(jid: string): string[] { + const set = new Set(); + for (const t of [ + 'paired_tasks', + 'work_items', + 'scheduled_tasks', + 'service_handoffs', + 'paired_projects', + ]) { + const rows = db + .query( + `SELECT DISTINCT group_folder FROM ${t} WHERE chat_jid=? AND group_folder IS NOT NULL`, + ) + .all(jid) as Array<{ group_folder: string | null }>; + for (const r of rows) if (r.group_folder) set.add(r.group_folder); + } + return [...set]; +} + const targets: Target[] = jids.map((jid) => { const r = db .query('SELECT folder, is_main, name FROM room_settings WHERE chat_jid=?') .get(jid) as | { folder: string | null; is_main: number | null; name: string | null } | undefined; + const folders = r?.folder ? [r.folder] : backtraceFolders(jid); return { jid, - folder: r?.folder ?? null, + folders, isMain: r?.is_main === 1, name: r?.name ?? null, registered: Boolean(r), @@ -102,15 +126,16 @@ function folderShared(folder: string): boolean { const safeFolders = new Set(); const sharedFolders = new Set(); for (const t of targets) { - if (!t.folder) continue; - if (folderShared(t.folder)) sharedFolders.add(t.folder); - else safeFolders.add(t.folder); + for (const folder of t.folders) { + if (folderShared(folder)) sharedFolders.add(folder); + else safeFolders.add(folder); + } } console.log('=== targets ==='); for (const t of targets) { console.log( - ` ${t.jid} | name=${t.name ?? '(unregistered)'} | folder=${t.folder ?? '-'} | registered=${t.registered}${t.isMain ? ' | MAIN' : ''}`, + ` ${t.jid} | name=${t.name ?? '(unregistered)'} | folder=${t.folders.length ? t.folders.join(',') : '-'} | registered=${t.registered}${t.isMain ? ' | MAIN' : ''}`, ); } if (sharedFolders.size) {