don't let the agent die when postgres blinks
resolveOwner already retried forever — but only when the query SUCCEEDED and returned nothing, which is a fresh install waiting on bootstrap. A query that THREW escaped the function, rejected the top-level await and exited the process, into exactly the PM2 restart loop its own comment says it exists to avoid. So any Postgres restart (57P03 'the database system is starting up') or moment of unavailability killed every live agent session on the machine and spun the sidecar until the database answered. That is what took a session down on 2026-08-10, and why this process showed 468 restarts against 0 for every peer that starts without needing the database. The loop now catches as well as checks. Still retries forever, matching the case beside it: a database coming back is a matter of time, and an agent that gave up would need a human to notice. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,13 +25,34 @@ import { getUserByEmail, getOwnerUser, getEmailAccounts } from 'officerdb';
|
||||
// "The owner" is not a simplification that multi-user will later invalidate. `chat` is an `execution`
|
||||
// capability (capabilities/registry.ts) and is never grantable at any level, so no account other than
|
||||
// the owner can ever reach this sidecar, however many accounts exist.
|
||||
//
|
||||
// ── Why this retries rather than throws ──
|
||||
//
|
||||
// There are two ways this can fail and it used to survive only one of them. A query that SUCCEEDS and
|
||||
// returns nothing is a fresh install waiting on POST /auth/bootstrap, and was handled. A query that
|
||||
// THROWS — Postgres restarting (`57P03: the database system is starting up`), or not up yet
|
||||
// (`ECONNREFUSED`) — escaped this function, rejected the top-level await, and exited the process into
|
||||
// exactly the PM2 restart loop the comment below says it exists to avoid. It spins until the database
|
||||
// answers, and every live agent session dies with the first crash.
|
||||
//
|
||||
// That is not hypothetical: it cost a session on 2026-08-10, and the restart counter on this sidecar
|
||||
// read 468 against 0 for every peer that starts without needing the database.
|
||||
//
|
||||
// Retrying forever rather than failing fast is deliberate, and matches the case below it: the database
|
||||
// coming back is a matter of time, and an agent that gave up would need a human to notice and restart it.
|
||||
async function resolveOwner() {
|
||||
const explicit = process.env.CLAUDE_USER_EMAIL?.trim();
|
||||
for (;;) {
|
||||
try {
|
||||
const user = explicit ? await getUserByEmail(explicit) : await getOwnerUser();
|
||||
if (user) return user;
|
||||
// Fresh install: wait for POST /auth/bootstrap instead of exiting into a PM2 restart loop.
|
||||
console.log(`[agent] no ${explicit ? `user "${explicit}"` : 'owner account'} yet — retrying in 5s`);
|
||||
} catch (err) {
|
||||
console.log(
|
||||
`[agent] database not reachable yet (${err instanceof Error ? err.message : String(err)}) — retrying in 5s`,
|
||||
);
|
||||
}
|
||||
await Bun.sleep(5_000);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user