The Background Write That Took the Server Down
A fire-and-forget promise rejects unhandled
Published Oct 29, 2026
Executive Summary
A vibe-coded Express app logs analytics after each response with a fire-and-forget promise that is never awaited or caught. When the database blips, the rejection becomes an unhandledRejection — which modern Node treats as a fatal error. A background failure takes the foreground down.
The Prompt
“Log analytics events after each request without slowing the response down.”
The Flaw
The promise returned by insertEvent is never awaited and never caught. If it rejects — the database blips, the connection pool is exhausted — the rejection is unhandled. Modern Node exits the process on an unhandled rejection by default; at best the events vanish with no trace. Either way, a failure in a background task takes down the request path it was supposed to stay out of.
Risk & Impact
[Availability] One flaky database write brings the whole server down, or the analytics silently stop recording with no error anywhere in the logs.
Cost: An outage caused by the one component nobody was watching, because it was 'background'.
Code Comparison
❌ Vulnerable (AI generated)
❌ Vulnerable (AI generated)
✅ Hardened (production ready)
The Fix
Catch (or await) every promise. A background task that can fail is still your responsibility — log and continue, or await it and fail the request explicitly.
Prevention
- [linter] typescript-eslint's no-floating-promises rule flags promises that are neither awaited nor caught.
- [ci] A test that makes insertEvent reject must show the process surviving and the error logged.
- [manual] Define the failure mode for every async call (rule 2) — 'what happens when this rejects?'
Why This Happened
Root cause: the prompt omitted Rule 2 — the guardrails that would have caught this. See the full guide →
