~/teardowns/TD-048.md
←Back to teardowns
# TD-048LowRuntimecase study

The Gate That Bounced Every Deep Link Before the Profile Loaded

A signed-in user loading /discover landed on the wrong home — because the invite gate decided before the profile existed

Published Nov 5, 2026

Executive Summary

On a cold load, the client builds a session from the auth token before the server profile has been fetched. That incomplete session carries default invite/onboarding flags, and the invite gate compared those defaults as if they were facts. A signed-in fan deep-linking to /discover was bounced /discover → /invite-gate → /audience. The fix marks the session as synced-or-not, never decides the gate on an unsynced session, and retries a failed bootstrap once after 5 seconds.

The Prompt

“Add an invite gate that redirects users who haven't redeemed their invite to the right persona home.”

The Flaw

On a cold load the session is built from the token before the server profile has been fetched, so `inviteRequired` and `inviteRedeemed` hold their defaults. The gate compared those defaults as if they were facts: `inviteRequired` defaulted truthy and `inviteRedeemed` falsy, so `needsInviteGate` fired and routed the user through `/invite-gate`. By the time the real profile landed, the router had already forwarded to the persona home — the requested deep link was lost. The agent treated 'not loaded yet' as 'not authorized'.

Risk & Impact

[Correctness] A signed-in fan loading /discover was bounced /discover → /invite-gate → /audience. Every cold deep link to a protected page landed on the wrong screen until the profile sync happened to win the race.

Cost: Dead deep links and a confusing first-run experience for every returning user on a cold load.

Code Comparison

❌ Vulnerable (AI generated)

frontend/src/shared/state/persona.tsts
1
2
3
4
5
6
// needsInviteGate — runs on every navigation before the session is ready
export function needsInviteGate(session: SessionState): boolean {
// A session built without the server profile carries DEFAULT flags here,
// not facts — but the gate treats them as the real thing.
return Boolean(session.accessToken && session.inviteRequired && !session.inviteRedeemed);
}

The Fix

Mark the session as synced-or-not; never decide the invite gate on an unsynced session; retry a failed bootstrap once after 5 seconds and return to the originally requested path.

Prevention

  • [manual] Any guard that reads async-loaded state must distinguish 'not loaded yet' from 'not authorized'.
  • [ci] E2E tests must cold-load deep links and assert the user lands on the requested route, not the persona home.

Why This Happened

Root cause: the prompt omitted Rule 5 — the guardrails that would have caught this. See the full guide →

Sources

Is your codebase vulnerable to this?

Run Diagnostic on My Project →