fix(login): fail fast with a clear error on slow/failed sign-in submit - #371
Merged
Conversation
Prod: an intermittent slow auth-chain hop (16-30s) left the login submit
button stuck on "Signing in..." with zero feedback, sometimes 401-ing
after ~19s with the user never told why. That 30s window came from the
shared axios instance timeout, which the login/signup POST rode along
with everything else.
- services/api.ts: bound authAPI.login()/signup() to a dedicated,
shorter LOGIN_TIMEOUT_MS (env-overridable, default 15s) via a
per-request axios timeout override, instead of the shared 30s.
- pages/LoginPage.tsx: distinguish a timeout/abort ("taking longer than
expected") from a 401 (worded so it doesn't wrongly accuse the user of
a typo, since a slow-auth abort can also surface as a 401) from other
failures; always reset `pending` in the submit's finally so the button
never stays stuck; show a quiet "still working..." hint after 8s for a
slow-but-succeeding attempt.
- Added LoginPage.submit-resilience.test.tsx covering both error paths
and the button un-sticking.
Verified in a real browser (Chrome DevTools MCP) against a mock auth
endpoint: a >timeout response shows the distinct timeout message within
the bounded window and re-enables the button; a 401 shows the ambiguous-
401 message; console clean aside from pre-existing unrelated mock-server
404s. Backend intermittent-401/Authentik latency is separate, out of
scope infra work.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session-Id: f636c22e-1cd7-401e-8843-97e3e3a4ba01
42 tasks
izzywdev
pushed a commit
that referenced
this pull request
Jul 29, 2026
…ideal one The client bound was 15s, chosen (#371) to fail FAST on a slow attempt rather than leave the submit button stuck on "Signing in…". That traded the wrong way. The documented slow path is 16-30s, so a 15s bound did not fail slow sign-ins fast — it failed sign-ins that would otherwise have SUCCEEDED. A bound below the known worst case is not a safety net, it is an outage, and that is what users are hitting. Client 15s -> 45s (VITE_LOGIN_TIMEOUT_MS), server budget 12s -> 40s (AUTHENTIK_LOGIN_DEADLINE_MS). 45s covers the documented worst case with real margin, and the server budget stays UNDER the client's so the server still answers first with a labelled error rather than being raced to an anonymous abort — that ordering is the invariant to preserve if either is retuned later. The wait is not pleasant. LoginPage already shows a "still working…" hint at 8s, so a slow-but-succeeding attempt does not look frozen. Both values stay env-overridable so the pair can be tightened again — without a rebuild — once the underlying slow hop is actually fixed, which is the real cure this is buying time for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhfKNASYBD9aS3Wu6RUPfZ
izzywdev
pushed a commit
that referenced
this pull request
Jul 29, 2026
Task 3 of the sign-in investigation: find the slowness. The blocker was that the evidence has never existed when it mattered. Per-hop timings were already instrumented — at `logger.debug`. LOG_LEVEL defaults to `info` and is not set anywhere in the chart, so in production that detail has always been OFF. Answering "which hop is slow?" needed a config change or a redeploy, during an incident, on a path that only misbehaves intermittently. That is why this module has collected two timeout band-aids (#362, #371) and no diagnosis. Adds a slow-hop threshold (the slow-query-log pattern): every hop is still debug on the fast path, but a hop over AUTHENTIK_SLOW_HOP_WARN_MS (1s) is logged at WARN with its stage label, status and elapsed time. No LOG_LEVEL change, no redeploy, no spam on healthy logins. The token-exchange stage is now timed the same way — it is two openid-client round-trips and just as able to be the slow one, so it must not be the one stage missing from the breakdown. Also reports a slow SUCCESS. A login that succeeds at 25s is exactly the failure mode that broke sign-in: it never errors, so nothing alerts, and it only became visible once a client bound tripped underneath it. Over AUTHENTIK_LOGIN_WARN_MS (8s, above the ~5.5s fast path) that now logs at WARN, and the per-hop WARNs say which stage owned the time. Leading hypothesis, written into the code so the logs can refute it rather than confirm a guess: every hop targets the same in-cluster origin, and this pod's own dnsConfig documents CoreDNS "intermittently stalls lookups in 5s/10s retry multiples" (capped ~2s by timeout:1/ attempts:2) with the note that this service "resolves authentik-server on every auth flow". DNS resolves per NEW CONNECTION, and the leaked response bodies fixed earlier in this PR forced a new connection per hop — so the stall was multiplied by hop count. ~6 hops x ~2s accounts for most of the observed 16-30s, and draining bodies lets undici reuse one keep-alive socket per origin, collapsing it to at most one. If the labelled elapsedMs still shows connect/DNS dominating, the next step is an explicit keep-alive dispatcher pinned to the Authentik origin. This is instrumentation, not a proven cure: it is what turns the next slow sign-in into a named stage instead of another guess. Tests: 1 new case — a slow hop on a login that SUCCEEDS emits exactly one WARN carrying the stage label and elapsed time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhfKNASYBD9aS3Wu6RUPfZ
izzywdev
pushed a commit
that referenced
this pull request
Aug 2, 2026
…ver-side login Every hop in the server-brokered password-login chain (flow-executor stages, authorize hops, admin-API set_password) lands on the same in-cluster Authentik origin, several times per request. The default fetch dispatcher's keep-alive window is tuned for general traffic and can lapse between hops when CoreDNS's documented multi-second stalls (see the recordHop comments in authentikPassword.ts) space them out, forcing a fresh connection — and a fresh DNS lookup — per hop. A dedicated undici Agent with a longer keep-alive holds one socket open across the whole chain, so only the first hop pays for DNS+connect. This was the last documented, unapplied mitigation in that file's own incident notes (#362, #371) for the intermittent 16-30s+ sign-in stalls that show up as an outright client-side timeout for some accounts.
izzywdev
added a commit
that referenced
this pull request
Aug 2, 2026
…ver-side login (#494) Every hop in the server-brokered password-login chain (flow-executor stages, authorize hops, admin-API set_password) lands on the same in-cluster Authentik origin, several times per request. The default fetch dispatcher's keep-alive window is tuned for general traffic and can lapse between hops when CoreDNS's documented multi-second stalls (see the recordHop comments in authentikPassword.ts) space them out, forcing a fresh connection — and a fresh DNS lookup — per hop. A dedicated undici Agent with a longer keep-alive holds one socket open across the whole chain, so only the first hop pays for DNS+connect. This was the last documented, unapplied mitigation in that file's own incident notes (#362, #371) for the intermittent 16-30s+ sign-in stalls that show up as an outright client-side timeout for some accounts. Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
LOGIN_TIMEOUT_MS, env-overridable, default 15s) instead of riding the shared 30s axios timeout — a slow auth-chain hop now fails fast instead of hanging.pendingresets infinallyon every exit path); a quiet "still working..." hint appears after 8s for a slow-but-succeeding attempt.LoginPage.submit-resilience.test.tsxcovering both error paths and the button un-sticking.Context
Prod bug: an intermittent slow Authentik auth-chain hop (16-30s, later diagnosed as DNS stalls to CoreDNS — infra, separate/out of scope) left the login submit silently stuck on "Signing in..." with zero feedback, sometimes 401-ing after ~19s with the user never told why.
Test plan
npx vitest run— 11/11 passing (2 new + 9 existing regression, incl.LoginPage.google-signin.test.tsx)npx tsc --noEmit— clean for both edited files (repo has pre-existing unrelated errors in sibling packages not touched by this change)/loginroute:/dashboardLOGIN_TIMEOUT_MS): shows "Sign-in is taking longer than expected — the service may be busy. Please try again." within the bounded window, button returns to "Sign In" (not stuck)/appsnot implemented in the mock)Out of scope
app-registry401Prod is deploy-on-push
master— not merging this myself; leaving for the orchestrator to merge in a deploy window.Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com