Parent: #1936
Problem
kickOne()/kickAll() in src/selfhost/sqlite-queue.ts fire pump() via bare void pump() with no .catch(), and pump()'s own try/finally only decrements the active counter — it does not catch, so any exception from claimNext() or reclaimExpiredProcessingJobs() (both run OUTSIDE processOne's own try block, e.g. a raw driver .query() failure) becomes an unhandled promise rejection. src/selfhost/pg-queue.ts has the identical void pump() pattern (more realistic there — Postgres connection drops/lock timeouts are more common than SQLite errors).
src/server.ts only registers process.on('unhandledRejection', ...) when SENTRY_DSN is set. In the (common, since Sentry is opt-in) case where SENTRY_DSN is unset, Node's default behavior for an unhandled rejection is to crash the process. A single transient DB error surfacing from claimNext()/reclaimExpiredProcessingJobs() would silently kill the entire self-host server with no metric bump, no Sentry event, and no /ready degradation warning beforehand — exactly the "quietly stops reviewing PRs" failure mode.
Fix
Wrap the body of pump() in a try/catch (or add .catch(error => { captureError(error, {kind:"queue_pump_crash"}); console.error(...); }) at each void pump() call site) so a claimNext()/reclaim failure is recorded and logged instead of propagating as an unhandled rejection, independent of whether Sentry is configured. Apply the same fix to both sqlite-queue.ts and pg-queue.ts.
Verification
Parent: #1936
Problem
kickOne()/kickAll()insrc/selfhost/sqlite-queue.tsfirepump()via barevoid pump()with no.catch(), andpump()'s own try/finally only decrements theactivecounter — it does not catch, so any exception fromclaimNext()orreclaimExpiredProcessingJobs()(both run OUTSIDEprocessOne's own try block, e.g. a raw driver.query()failure) becomes an unhandled promise rejection.src/selfhost/pg-queue.tshas the identicalvoid pump()pattern (more realistic there — Postgres connection drops/lock timeouts are more common than SQLite errors).src/server.tsonly registersprocess.on('unhandledRejection', ...)whenSENTRY_DSNis set. In the (common, since Sentry is opt-in) case whereSENTRY_DSNis unset, Node's default behavior for an unhandled rejection is to crash the process. A single transient DB error surfacing fromclaimNext()/reclaimExpiredProcessingJobs()would silently kill the entire self-host server with no metric bump, no Sentry event, and no/readydegradation warning beforehand — exactly the "quietly stops reviewing PRs" failure mode.Fix
Wrap the body of
pump()in a try/catch (or add.catch(error => { captureError(error, {kind:"queue_pump_crash"}); console.error(...); })at eachvoid pump()call site) so aclaimNext()/reclaim failure is recorded and logged instead of propagating as an unhandled rejection, independent of whether Sentry is configured. Apply the same fix to bothsqlite-queue.tsandpg-queue.ts.Verification
pump()/kickOne/kickAllimplementation against both files (line numbers may have shifted).claimNext()throw and confirms the process does NOT crash (no unhandled rejection), and the failure is logged/recorded.reclaimExpiredProcessingJobs().