← back to George Gmail
Schedule daily info@ old-draft drain (launchd) with runaway cap + heartbeat
0fd5c29e2b339eb6c4cd3202369e39e30421d697 · 2026-08-20 11:18:43 -0700 · Steve Abrams
- Daily 04:15 launchd job com.steve.george-drain-old-drafts runs the drain
in CONFIRM=1 to keep info@ drafts >30d trimmed automatically.
- Runaway guard: MAX_AUTO_DELETE=800 — if a run ever finds a huge backlog it
refuses to auto-delete and emits WARN for human review (never silent mass-nuke).
- Heartbeat data/drain-old-drafts-latest.json in PASS/WARN/FAIL vocabulary for
the fleet-health rollup + meta-watchdog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
A com.steve.george-drain-old-drafts.plistM delete-old-drafts-info.js
Diff
commit 0fd5c29e2b339eb6c4cd3202369e39e30421d697
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Aug 20 11:18:43 2026 -0700
Schedule daily info@ old-draft drain (launchd) with runaway cap + heartbeat
- Daily 04:15 launchd job com.steve.george-drain-old-drafts runs the drain
in CONFIRM=1 to keep info@ drafts >30d trimmed automatically.
- Runaway guard: MAX_AUTO_DELETE=800 — if a run ever finds a huge backlog it
refuses to auto-delete and emits WARN for human review (never silent mass-nuke).
- Heartbeat data/drain-old-drafts-latest.json in PASS/WARN/FAIL vocabulary for
the fleet-health rollup + meta-watchdog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
com.steve.george-drain-old-drafts.plist | 43 +++++++++++++++++++++++++++++++++
delete-old-drafts-info.js | 15 ++++++++++++
2 files changed, 58 insertions(+)
diff --git a/com.steve.george-drain-old-drafts.plist b/com.steve.george-drain-old-drafts.plist
new file mode 100644
index 0000000..f417846
--- /dev/null
+++ b/com.steve.george-drain-old-drafts.plist
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Label</key>
+ <string>com.steve.george-drain-old-drafts</string>
+
+ <key>ProgramArguments</key>
+ <array>
+ <string>/opt/homebrew/bin/node</string>
+ <string>/Users/macstudio3/Projects/george-gmail/delete-old-drafts-info.js</string>
+ </array>
+
+ <key>EnvironmentVariables</key>
+ <dict>
+ <key>CONFIRM</key>
+ <string>1</string>
+ <!-- Runaway guard: if >800 drafts are >30d in one run, skip auto-delete and WARN for human review. -->
+ <key>MAX_AUTO_DELETE</key>
+ <string>800</string>
+ <key>PATH</key>
+ <string>/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
+ </dict>
+
+ <key>WorkingDirectory</key>
+ <string>/Users/macstudio3/Projects/george-gmail</string>
+
+ <!-- Daily at 04:15 (low George/Gmail traffic; after the initial cleanup the daily backlog is small). -->
+ <key>StartCalendarInterval</key>
+ <dict>
+ <key>Hour</key><integer>4</integer>
+ <key>Minute</key><integer>15</integer>
+ </dict>
+
+ <key>StandardOutPath</key>
+ <string>/Users/macstudio3/Projects/george-gmail/logs/drain-old-drafts.out.log</string>
+ <key>StandardErrorPath</key>
+ <string>/Users/macstudio3/Projects/george-gmail/logs/drain-old-drafts.err.log</string>
+
+ <key>RunAtLoad</key>
+ <false/>
+</dict>
+</plist>
diff --git a/delete-old-drafts-info.js b/delete-old-drafts-info.js
index d94fb98..d083582 100644
--- a/delete-old-drafts-info.js
+++ b/delete-old-drafts-info.js
@@ -36,6 +36,13 @@ const PAGE = parseInt(process.env.PAGE || '40', 10);
const PAGE_SLEEP = parseInt(process.env.PAGE_SLEEP || '3000', 10);
const DEL_SLEEP = parseInt(process.env.DEL_SLEEP || '250', 10);
const MAX_BATCHES = parseInt(process.env.MAX_BATCHES || '40', 10);
+// Runaway guard for unattended runs: if the >30d set exceeds this, DO NOT
+// auto-delete — flag for a human. 0/unset = no cap (manual runs).
+const MAX_AUTO_DELETE = parseInt(process.env.MAX_AUTO_DELETE || '0', 10);
+const HB_PATH = path.join(__dirname, 'data', 'drain-old-drafts-latest.json');
+function heartbeat(obj) {
+ try { fs.writeFileSync(HB_PATH, JSON.stringify({ ts: new Date().toISOString(), skill: 'delete-old-drafts-info', account: ACC, ...obj }, null, 2)); } catch (_) {}
+}
function resolveAuth() {
if (process.env.GEORGE_AUTH && process.env.GEORGE_AUTH.includes(':')) {
@@ -114,6 +121,13 @@ async function discoverSet() {
return;
}
+ // ---- runaway guard (unattended runs) ----
+ if (MAX_AUTO_DELETE > 0 && older.size > MAX_AUTO_DELETE) {
+ console.error(`ABORT: >30d set (${older.size}) exceeds MAX_AUTO_DELETE (${MAX_AUTO_DELETE}). Not auto-deleting — run manually to review.`);
+ heartbeat({ verdict: 'WARN', status: 'WARN', deleted: 0, failed: 0, older_set: older.size, cap: MAX_AUTO_DELETE, note: 'backlog exceeds cap; auto-delete skipped, human review needed' });
+ process.exit(0);
+ }
+
// ---- DRAIN LOOP ----
const logFile = `/tmp/george-old-drafts-deleted-${stamp}.jsonl`;
let totalOk = 0, totalFail = 0, batch = 0;
@@ -138,4 +152,5 @@ async function discoverSet() {
console.log(`\n\nDONE. deleted=${totalOk} failed=${totalFail} remaining-in-set=${older.size}`);
console.log(`Deletion log: ${logFile}`);
if (older.size > 0) console.log('Some >30d drafts remain (batch cap or failures). Re-run to finish.');
+ heartbeat({ verdict: totalFail > 0 ? 'WARN' : 'PASS', status: totalFail > 0 ? 'WARN' : 'PASS', deleted: totalOk, failed: totalFail, remaining_in_set: older.size });
})().catch((e) => { console.error('FATAL:', e.message); process.exit(1); });
← 7106f29 Add drain script: permanently delete info@ drafts older than
·
back to George Gmail
·
auto-data-snapshot: 2026-08-20T11:21:15 (1 data files) — dat 9fa1a8a →