Skip to content

autopilot: reconnect path calls connect() with no args → FATAL restart-loop on every DB blip #2034

@brandobuilds

Description

@brandobuilds

Bug

After any transient DB error (network blip, Supabase pooler restart, idle-connection drop), the autopilot daemon FATALs with:

[autopilot] FATAL: unrecoverable DB error (undefined is not an object (evaluating 'config.database_url')). Exiting so launchd ThrottleInterval can apply backoff.

The launchd ThrottleInterval safety net (v0.37.7.0) catches this and restarts the daemon, but the underlying reconnect path never succeeds — it exits on every attempt, making "reconnect" effectively a process-restart loop.

Found on v0.37.11.0 (2026-05-22); confirmed still present at v0.42.1.0 (eefe8b57) — same bare call at src/commands/autopilot.ts:337.

Repro

Run autopilot against any Postgres brain. Wait for a network blip or for the Supabase pooler to recycle the connection (typically <24h). Check ~/.gbrain/autopilot.err:

[reconnect] ERROR: undefined is not an object (evaluating 'config.database_url')
[autopilot] FATAL: unrecoverable DB error ...

Root cause

src/commands/autopilot.ts:337 (at eefe8b57):

await (engine as any).connect?.();

The bare connect?.() call passes NO arguments. PostgresEngine.connect(config) immediately dereferences config.database_url, which throws when config is undefined.

The engine already has a proper reconnect() method (src/core/postgres-engine.ts:4653 at eefe8b57) that reuses this._savedConfig and needs no argument.

Fix

@@ src/commands/autopilot.ts:336-337
-        await engine.disconnect();
-        await (engine as any).connect?.();
+        await (engine as any).reconnect?.();

reconnect() handles disconnect + connect with the saved config internally.

Impact

The classifyReconnectError defense from #1162 bails cleanly so launchd backs off — correct safety net, but the underlying reconnect never works. Autopilot reboots on every DB blip instead of recovering in-process.

We run this one-line patch locally; verified across multiple pooler recycles — daemon survives blips in-process.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions