fix(auth): read login error param synchronously to close a render race - #3034
fra-shipper wants to merge 2 commits into
Conversation
Login read the ?error= query param via a useEffect, so the first render always had error=''. That flowed into LoginForm's own useState(error) initial value, and LoginForm only caught up once both components' effects had flushed -- a two-hop async chain before the [role=alert] error message ever appears in the DOM. Initialize the state from the query param directly (lazy useState initializer) so the first render already carries it, removing one of the two async hops. The existing effect is kept to re-sync error if the query changes after mount. Added Login.spec.tsx: mounts <Login> with createRoot + flushSync, bypassing Testing Library's act()-wrapped render() (which flushes effects synchronously and would hide the bug), and asserts the [role="alert"] node is present on the very first commit. Confirmed it fails on the pre-fix code and passes with this change. This is the same class of bug as the thread_resume race fixed in Chainlit#3021 (a stale-DOM assertion racing an async render). It is a plausible but unconfirmed contributor to Chainlit#3023: a single windows-latest CI occurrence where the oauth_auth spec's 'shows a specific message for oauthSignin error' test hit the 30s Cypress command timeout on attempt 1 and passed on retry, filed needs-triage with the root cause still unconfirmed. The render gap this fix closes resolves within a single synchronous commit, orders of magnitude smaller than a 30s command timeout, so this is not a confirmed diagnosis of Chainlit#3023 -- it is a genuine render-timing bug worth fixing on its own merits, in the same code path Chainlit#3023's test exercises.
There was a problem hiding this comment.
All reported issues were addressed across 2 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Both createRoot-based tests left their React root mounted after the test; the shared cleanup() in tests/setup-tests.ts only handles trees rendered via Testing Library's render(), so the manual roots and Login's pending passive effect leaked across tests. Track the root and unmount it in afterEach, before removing the container.
|
Addressed the cubic review finding: Login.spec.tsx's two manual Verified: |
Refs #3023.
Root cause
Loginread the?error=query param via auseEffect, soerroralways started as''on the first render. That empty value flowed intoLoginForm's ownuseState(error)initial value.LoginFormonly picked up the real error message once both components' effects had flushed:Loginmounts witherror=''.Login'suseEffectfires, callingsetError(query.get('error') || ''), re-renderingLoginand passing the correcterrorprop toLoginForm.LoginForm's ownuseEffect(which syncserrorStatefrom theerrorprop) fires and finally updateserrorState, causing the[role="alert"]element to appear.That is a two-hop async chain between two components' effects before the error message exists in the DOM at all -- the same class of stale-DOM-vs-async-render bug already diagnosed and fixed for
thread_resumein #3021, just at the login page instead of the chat composer.#3023 reports a single
windows-latestCI occurrence where theoauth_authspec'sshows a specific message for oauthSignin errortest hit Cypress's 30sdefaultCommandTimeouton attempt 1 and passed on retry. This render gap resolves within a single synchronous commit -- orders of magnitude smaller than a 30s command timeout -- so this PR does not claim to be a confirmed diagnosis of #3023. It fixes a genuine render-timing bug in the exact code path that test exercises; I could not reproduce the CI flake locally to confirm it is the same mechanism (a single occurrence, unknown rate, Windows-only real-browser timing).Fix
Initialize
errorfrom the query param synchronously with a lazyuseStateinitializer, instead of viauseEffect. The first render now already carries the correct value, soLoginForm's initialerrorStateis correct from its very first render too -- no async hop is needed for the initial-load case. The existinguseEffectis kept soerrorstill re-syncs if the query string changes while the component stays mounted.Testing
pnpm lint frontend/src/pages/Login.tsx-- clean.pnpm format-check:files frontend/src/pages/Login.tsx-- "Checking formatting... All matched files use Prettier code style!"pnpm --filter @chainlit/app type-check(after buildinglibs/react-client, a pre-existing workspace build-order requirement) -- 0 errors; on unmodified HEAD viagit stashthe same command produced 262 pre-existingerror TS...lines from the unbuilt@chainlit/react-client/client-typesworkspace package, confirming those are pre-existing and unrelated to this change.frontend/tests/Login.spec.tsx: mounts<Login>withcreateRoot+flushSync(bypassing React Testing Library'sact()-wrappedrender(), which flushes effects synchronously and would hide the bug) and asserts[role="alert"]is present on the very first commit when mounted with?error=..., plus a negative-control assertion for the no-error-param case. Confirmed this fails on pre-fixLogin.tsx(AssertionError: expected null not to be null) and passes post-fix.pnpm --filter @chainlit/app test--Test Files 1 failed | 5 passed (6),Tests 3 failed | 31 passed (34). The one failing file (displayModePrecedence.spec.ts, a jsdom/localStorage environment issue) is identical before and after this change -- re-verified on bothHEADandHEAD~1'sLogin.tsx-- and unrelated toLogin.tsx.pnpm --filter @chainlit/app type-checkon staged files) ran automatically ongit commitand passed without--no-verify.Not run: Cypress e2e (
cypress/e2e/oauth_auth). It requires spinning up a full Chainlit backend viauv run chainlit run ...percypress.config.ts'sbefore:spechook, and more importantly the flake this fix targets is a Windows-only, real-browser timing race with a single observed occurrence that I have no way to force or deterministically re-verify locally either way.Summary by cubic
Fixes a login render race that could delay the OAuth error message until after two effects; it now appears on the first commit. This covers the code path in #3023, though the reported CI flake remains unconfirmed.
?error=synchronously and still re-syncs when the query changes.createRootandflushSync.Written for commit 10428c5. Summary will update on new commits.