Real incident: a session-flush refactor introduced a missing await. Production sessions stayed stale; 80% of authenticated requests saw the wrong user context. 40-minute outage. Mesrai caught the same shape on a follow-up PR.
The vulnerable diff
// auth/session-flush.ts (the buggy refactor)
export async function flushSession(userId: string, session: Session) {
const sessionKey = `session:${userId}`;
// BUG: missing await — function returns before Redis ack
redis.set(sessionKey, JSON.stringify(session));
return { ok: true };
}
// Caller:
await flushSession(user.id, newSession);
const cached = await redis.get(`session:${user.id}`);
// cached is the OLD session — flushSession's redis.set hadn't completed.What is wrong
Missing await is one of the most common JS bugs and the most damaging when it affects state synchronization. The function returns before the actual write completes; downstream code that depends on the new state sees the old state. In session-flush specifically, the impact is that user changes (logout, role change, settings update) don't take effect — users see the old data and act on it, sometimes performing actions they shouldn't be allowed to.
The attack
Post-mortem timeline:
t+0:00 — Deploy. Session flush PR live.
t+0:01 — Error rate climbs. 401s on freshly updated sessions.
t+0:03 — Pager. Engineer triaging. Hypothesis: Redis slow.
t+0:08 — Redis fine. Switch to "Caching layer issue."
t+0:15 — Re-read the diff. Spot the missing await.
t+0:18 — Hotfix push, await added.
t+0:25 — Hotfix deployed.
t+0:40 — Error rate back to baseline.
Lesson: floating promise detection at PR time would have prevented
the whole outage.Cost: ~40 minutes of degraded service, customer apology email.
Mesrai's review comment
mesraipilot · Bot · reviewed 30 sec ago
[mesrai] [code-review] [Logic] [Floating-Promise] [critical]
`redis.set(...)` returns a Promise. Caller does not await — function
returns before the write completes; downstream `redis.get` reads
the OLD value.
Fix:
await redis.set(sessionKey, JSON.stringify(session));
Enable @typescript-eslint/no-floating-promises in your config to
catch this class at lint time:
"rules": {
"@typescript-eslint/no-floating-promises": "error"
}The fix
// auth/session-flush.ts — fixed
export async function flushSession(userId: string, session: Session) {
const sessionKey = `session:${userId}`;
await redis.set(sessionKey, JSON.stringify(session));
return { ok: true };
}Add the await. Lint rule on no-floating-promises catches the class going forward. Mesrai's rule pack is a backstop when the lint rule isn't enforced.
Why human review missed it
Floating promises are everywhere in async-heavy code. Mesrai catches every dropped promise return — the same way ESLint does, but as part of review instead of post-commit lint.
Related rules + further reading
Mesrai rule pack: logic/no-floating-promises — flags dropped Promise returns.
@typescript-eslint/no-floating-promises — ESLint rule covering the same class.
Most common cause of state-sync bugs in async TS.
Takeaway
Missing await caused a 40-min outage. Mesrai's rule pack would have caught at PR time. ESLint can too — enforce it.