fix: graceful shutdown — write last-run file on SIGTERM, show interrupted state in status

This commit is contained in:
2026-03-06 00:35:10 +00:00
parent 1920df51a4
commit b091473735
4 changed files with 48 additions and 19 deletions

View File

@@ -27,7 +27,12 @@ import {
const isPreview = process.argv.includes('--preview');
async function main() {
acquireLock('applier', resolve(__dir, 'data'));
const lock = acquireLock('applier', resolve(__dir, 'data'));
lock.onShutdown(() => {
writeFileSync(resolve(__dir, 'data/applier_last_run.json'), JSON.stringify({
finished_at: null, finished: false, note: 'interrupted'
}, null, 2));
});
console.log('🚀 claw-apply: Job Applier starting\n');
const settings = loadConfig(resolve(__dir, 'config/settings.json'));

View File

@@ -20,9 +20,30 @@ import { DEFAULT_FIRST_RUN_DAYS } from './lib/constants.mjs';
import { generateKeywords } from './lib/keywords.mjs';
async function main() {
acquireLock('searcher', resolve(__dir, 'data'));
const lock = acquireLock('searcher', resolve(__dir, 'data'));
console.log('🔍 claw-apply: Job Searcher starting\n');
let totalAdded = 0, totalSeen = 0;
const platformsRun = [];
const startedAt = Date.now();
const writeLastRun = (finished = false) => {
writeFileSync(resolve(__dir, 'data/searcher_last_run.json'), JSON.stringify({
started_at: startedAt,
finished_at: finished ? Date.now() : null,
finished,
added: totalAdded,
seen: totalSeen,
skipped_dupes: totalSeen - totalAdded,
platforms: platformsRun,
}, null, 2));
};
lock.onShutdown(() => {
console.log(' Writing partial results to last-run file...');
writeLastRun(false);
});
// Load config
const settings = loadConfig(resolve(__dir, 'config/settings.json'));
const searchConfig = loadConfig(resolve(__dir, 'config/search_config.json'));
@@ -51,10 +72,6 @@ async function main() {
const lookbackDays = isFirstRun ? (searchConfig.first_run_days || DEFAULT_FIRST_RUN_DAYS) : null;
if (isFirstRun) console.log(`📅 First run — looking back ${lookbackDays} days\n`);
let totalAdded = 0;
let totalSeen = 0;
const platformsRun = [];
// Group searches by platform
const liSearches = searchConfig.searches.filter(s => s.platforms?.includes('linkedin'));
const wfSearches = searchConfig.searches.filter(s => s.platforms?.includes('wellfound'));
@@ -136,14 +153,7 @@ async function main() {
console.log(`\n${summary.replace(/\*/g, '')}`);
if (totalAdded > 0) await sendTelegram(settings, summary);
// Write last-run metadata for status.mjs
writeFileSync(resolve(__dir, 'data/searcher_last_run.json'), JSON.stringify({
finished_at: Date.now(),
added: totalAdded,
seen: totalSeen,
skipped_dupes: totalSeen - totalAdded,
platforms: platformsRun,
}, null, 2));
writeLastRun(true);
console.log('\n✅ Search complete');
return { added: totalAdded, seen: totalSeen };

View File

@@ -26,9 +26,21 @@ export function acquireLock(name, dataDir) {
const release = () => {
try { unlinkSync(lockFile); } catch {}
};
process.on('exit', release);
process.on('SIGINT', () => { release(); process.exit(130); });
process.on('SIGTERM', () => { release(); process.exit(143); });
return release;
// Graceful shutdown — call registered cleanup before exiting
const shutdownHandlers = [];
const shutdown = (code) => async () => {
console.log(`\n⚠️ ${name}: signal received, shutting down gracefully...`);
for (const fn of shutdownHandlers) {
try { await fn(); } catch {}
}
release();
process.exit(code);
};
process.on('exit', release);
process.on('SIGINT', shutdown(130));
process.on('SIGTERM', shutdown(143));
return { release, onShutdown: (fn) => shutdownHandlers.push(fn) };
}

View File

@@ -108,7 +108,9 @@ function formatReport(s) {
const sr = s.searcher;
const searcherLine = sr.running
? `🔄 Running now — ${q.total} jobs found so far`
: `⏸️ Last ran ${timeAgo(sr.last_run?.finished_at)}`;
: sr.last_run?.finished === false
? `⚠️ Last run interrupted ${timeAgo(sr.last_run?.started_at)} (partial results saved)`
: `⏸️ Last ran ${timeAgo(sr.last_run?.finished_at)}`;
const lastRunDetail = sr.last_run && !sr.running
? ` Found ${sr.last_run.added} new jobs (${sr.last_run.seen} seen, ${sr.last_run.skipped_dupes || 0} dupes)`
: null;