the agent sidecar crashed on boot without the email plugin

user-instance.ts read `getEmailAccounts` at module level, unguarded, in a
top-level await. Commenting the email schema out earlier tonight means
`email_accounts` does not exist on a core install, so that query throws, the
rejection escapes, and officer-agent exits — into the PM2 restart loop, taking
chat with it. Chat is core; the agent sidecar is what spawns `claude`.

The same file's header documents this exact failure being fixed once already, for
`resolveOwner`: "A query that THROWS — Postgres restarting, or not up yet —
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." That cost
a session on 2026-08-10. This line has the identical shape and was never covered,
because until tonight the table always existed.

Guarded now: no accounts, a warning, and the email_db tool sits idle. An agent
must start without email — it is one tool, not a prerequisite for chat.

Checked the rest while there: the only other top-level awaits in the core
processes are resolveOwner (already hardened), sign (now backed by the secret
store, which creates on demand) and assertSecretsClosed (throws on purpose).

The three vault calls in core auth — signout, revoke, panic — are
`clearVaultTokens(...).catch(() => {})`, fire-and-forget, so a missing table is
swallowed. They are fine as they stand.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-13 02:16:34 +00:00
co-authored by Claude Opus 5
parent 3cb39662b5
commit fbb6d6c78c
+13 -1
View File
@@ -86,7 +86,19 @@ const homeDir = homedir();
const globalToolsDir = join(DATA_PATH, 'tools');
// The email_db MCP tool reads one account's SQLite store; match the API's choice (first enabled).
const emailAccounts = await getEmailAccounts(dbUser.id);
//
// Guarded for the same reason `resolveOwner` above is, and it is the same bug in a second place: this
// is a top-level await, so a query that THROWS rejects it and exits the process — straight into the PM2
// restart loop, taking every live agent session with it. `resolveOwner` was hardened after that cost a
// session on 2026-08-10; this line has the identical shape and was not.
//
// It throws for real now: email is a plugin, so `email_accounts` is commented out of the schema
// (officerdb/src/schema.ts) and does not exist on a core install. An agent must start without it — the
// email tool is one tool, not a prerequisite for chat.
const emailAccounts = await getEmailAccounts(dbUser.id).catch(() => {
console.warn('[agent] no email accounts (the email plugin is not installed) — the email_db tool will be idle');
return [] as Awaited<ReturnType<typeof getEmailAccounts>>;
});
const emailAccount = emailAccounts.find((a) => a.enabled) ?? emailAccounts[0];
const emailDbRel = join('email_accounts', emailAccount?.email ?? 'none', 'emails.db');
const userToolsDir = join(DATA_PATH, email, 'tools');