Mesrai
Back to blog
// essayTechnical Deep Dive

A Production-Wide Outage That Started as a Missing await

Real incident: session flush missing await — 80% error rate, 40-minute outage. Mesrai catch of the same shape.

Mesrai TeamAugust 31, 20268 min read

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

tsauth/session-flush.ts
// 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:

textexploit
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

textreview-comment.txt
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

tsauth/session-flush.ts (fixed)
// 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.

// try

See it on your next PR.

Free for individuals. Install in two minutes. Mesrai reviews every commit.