fix(scripts): prune scheduled-task run logs + auto-backup on deregister
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-<ts>.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 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
* channel record (chats) and its message history (messages).
|
* channel record (chats) and its message history (messages).
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* bun scripts/deregister-room.ts [--dry-run] [--force] <channelId|jid> [more...]
|
* bun scripts/deregister-room.ts [--dry-run] [--force] [--no-backup] <channelId|jid> [more...]
|
||||||
*
|
*
|
||||||
* What it removes for each target chat:
|
* What it removes for each target chat:
|
||||||
* - room registration + role/skill overrides + channel owner lease
|
* - 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
|
* - a group folder shared by another remaining room is NOT deleted on disk
|
||||||
* (only that chat's DB rows are removed)
|
* (only that chat's DB rows are removed)
|
||||||
* - --dry-run reports what would change without writing anything
|
* - --dry-run reports what would change without writing anything
|
||||||
|
* - the DB is auto-backed up to /home/claude/ejclaw-db-backup-<ts>.db before
|
||||||
|
* the purge unless --no-backup is passed
|
||||||
*/
|
*/
|
||||||
import { execFileSync } from 'child_process';
|
import { execFileSync } from 'child_process';
|
||||||
import fs from 'fs';
|
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 args = process.argv.slice(2);
|
||||||
const dryRun = args.includes('--dry-run');
|
const dryRun = args.includes('--dry-run');
|
||||||
const force = args.includes('--force');
|
const force = args.includes('--force');
|
||||||
|
const noBackup = args.includes('--no-backup');
|
||||||
const ids = args.filter((a) => !a.startsWith('--'));
|
const ids = args.filter((a) => !a.startsWith('--'));
|
||||||
|
|
||||||
if (ids.length === 0) {
|
if (ids.length === 0) {
|
||||||
console.error(
|
console.error(
|
||||||
'Usage: bun scripts/deregister-room.ts [--dry-run] [--force] <channelId|jid> [more...]',
|
'Usage: bun scripts/deregister-room.ts [--dry-run] [--force] [--no-backup] <channelId|jid> [more...]',
|
||||||
);
|
);
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
}
|
}
|
||||||
@@ -126,6 +129,14 @@ const taskIds = (
|
|||||||
}>
|
}>
|
||||||
).map((r) => r.id);
|
).map((r) => r.id);
|
||||||
const qT = taskIds.map(() => '?').join(',');
|
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 safeFolderList = [...safeFolders];
|
||||||
const qF = safeFolderList.map(() => '?').join(',');
|
const qF = safeFolderList.map(() => '?').join(',');
|
||||||
|
|
||||||
@@ -158,7 +169,17 @@ const addTask = (t: string) => {
|
|||||||
addTask('paired_turn_attempts');
|
addTask('paired_turn_attempts');
|
||||||
addTask('paired_turn_outputs');
|
addTask('paired_turn_outputs');
|
||||||
addTask('paired_turns');
|
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 [
|
for (const t of [
|
||||||
'paired_task_execution_leases',
|
'paired_task_execution_leases',
|
||||||
'paired_turn_reservations',
|
'paired_turn_reservations',
|
||||||
@@ -222,6 +243,19 @@ if (dryRun) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- execute ---
|
// --- 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');
|
db.exec('BEGIN');
|
||||||
try {
|
try {
|
||||||
for (const p of plan) p.run();
|
for (const p of plan) p.run();
|
||||||
|
|||||||
Reference in New Issue
Block a user