From e21308db1f816243a30accb60005432c13209ac0 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 18 Aug 2026 23:38:35 +0900 Subject: [PATCH] fix(scripts): prune scheduled-task run logs + auto-backup on deregister MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit task_run_logs.task_id references scheduled_tasks.id, not paired_tasks.id, so the previous deletion (keyed by paired task ids) left orphaned run-log rows behind — with foreign_keys OFF nothing cleaned them up. Delete task_run_logs by the chat's scheduled_tasks ids before removing the scheduled_tasks rows. Also auto-back up the DB to /home/claude/ejclaw-db-backup-.db before the irreversible purge (skippable with --no-backup). Verified end-to-end in a sandboxed DB copy: a seeded room's scheduled task + 3 run logs, paired data, work items, sessions, and router cursor are all removed, disk folders deleted, while chats/messages and unrelated rooms stay intact. Co-Authored-By: Claude Opus 4.7 --- scripts/deregister-room.ts | 40 +++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/scripts/deregister-room.ts b/scripts/deregister-room.ts index ab2c3b2..0729930 100644 --- a/scripts/deregister-room.ts +++ b/scripts/deregister-room.ts @@ -4,7 +4,7 @@ * channel record (chats) and its message history (messages). * * Usage: - * bun scripts/deregister-room.ts [--dry-run] [--force] [more...] + * bun scripts/deregister-room.ts [--dry-run] [--force] [--no-backup] [more...] * * What it removes for each target chat: * - room registration + role/skill overrides + channel owner lease @@ -24,6 +24,8 @@ * - a group folder shared by another remaining room is NOT deleted on disk * (only that chat's DB rows are removed) * - --dry-run reports what would change without writing anything + * - the DB is auto-backed up to /home/claude/ejclaw-db-backup-.db before + * the purge unless --no-backup is passed */ import { execFileSync } from 'child_process'; import fs from 'fs'; @@ -36,11 +38,12 @@ import { DATA_DIR, GROUPS_DIR, STORE_DIR } from '../src/config.js'; const args = process.argv.slice(2); const dryRun = args.includes('--dry-run'); const force = args.includes('--force'); +const noBackup = args.includes('--no-backup'); const ids = args.filter((a) => !a.startsWith('--')); if (ids.length === 0) { console.error( - 'Usage: bun scripts/deregister-room.ts [--dry-run] [--force] [more...]', + 'Usage: bun scripts/deregister-room.ts [--dry-run] [--force] [--no-backup] [more...]', ); process.exit(2); } @@ -126,6 +129,14 @@ const taskIds = ( }> ).map((r) => r.id); const qT = taskIds.map(() => '?').join(','); +// task_run_logs.task_id references scheduled_tasks.id (NOT paired_tasks.id), so +// its rows must be pruned by the chat's scheduled task ids. +const scheduledTaskIds = ( + db + .query(`SELECT id FROM scheduled_tasks WHERE chat_jid IN (${qJ})`) + .all(...jids) as Array<{ id: string }> +).map((r) => r.id); +const qS = scheduledTaskIds.map(() => '?').join(','); const safeFolderList = [...safeFolders]; const qF = safeFolderList.map(() => '?').join(','); @@ -158,7 +169,17 @@ const addTask = (t: string) => { addTask('paired_turn_attempts'); addTask('paired_turn_outputs'); addTask('paired_turns'); -addTask('task_run_logs'); +// Prune scheduled-task run logs before the scheduled_tasks rows they belong to. +if (scheduledTaskIds.length) { + plan.push({ + table: 'task_run_logs', + n: count('task_run_logs', `task_id IN (${qS})`, scheduledTaskIds), + run: () => + db + .query(`DELETE FROM task_run_logs WHERE task_id IN (${qS})`) + .run(...scheduledTaskIds), + }); +} for (const t of [ 'paired_task_execution_leases', 'paired_turn_reservations', @@ -222,6 +243,19 @@ if (dryRun) { } // --- execute --- +// Auto-backup the DB before the irreversible purge (skip with --no-backup). +if (!noBackup) { + const dbFile = path.join(STORE_DIR, 'messages.db'); + const stamp = new Date() + .toISOString() + .replace(/[-:]/g, '') + .replace(/\..+$/, '') + .replace('T', '-'); + const backup = path.join('/home/claude', `ejclaw-db-backup-${stamp}.db`); + fs.copyFileSync(dbFile, backup); + console.log(`DB backed up to ${backup}`); +} + db.exec('BEGIN'); try { for (const p of plan) p.run();