You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Onboarding dialog stuck: 'Get started' button does nothing after invitation sign-in via Google #384
When onboarding a new user via an email invitation sent to a private (non-organization) email address, the final Get started button on the onboarding dialog does nothing — the user remains stuck on the dialog and never reaches the portal.
Steps to reproduce
Invite a user via their private email address.
User receives the invitation email and follows the link.
User signs in via Google (sign-in itself works).
The onboarding dialog appears; user fills in all requested fields.
User clicks Get started.
Expected behavior
Onboarding completes and the user is taken into the portal.
Actual behavior
Nothing visibly happens. No error message, no navigation — the onboarding dialog stays open and the user is stuck. Retrying the button has no effect.
Notes / possible avenue
The symptom ('button does nothing', no feedback) matches the known hub-deadlock pattern documented in CLAUDE.md: an await on a hub-backed service inside a click action or hub handler instead of the reactive IObservable + Subscribe pattern. Worth checking the onboarding dialog's Get-started click handler and the user-provisioning service it calls (mesh node creation for the new user) for await/.ToTask()/FirstAsync() usage.
Also worth verifying whether the failure is specific to invitation-based accounts with external (Google) identities — e.g. the user-provisioning write failing silently due to missing access context for a not-yet-provisioned user.
Reported by @rsalzmann (reproduced with a private email + Google sign-in).
Primary root cause — already fixed before this issue was filed. The submit path works (Onboarding.razor:295 HandleSubmit → the 4-query CombineLatest → UserOnboardingService.CreateUser/GrantSelfAdmin → OnOnboardingSucceeded forced reload). The failure was that on the forced reload OnboardingMiddleware.FindUserByEmail's workspace.GetQuery("auth:userByEmail:{email}") replays a permanently-cached EMPTY snapshot (pg_notify disabled on the partitioned portal → GetQuery is Replay(1) with no live delta), so the just-onboarded user is bounced back to /onboarding ("button did nothing / cannot advance"). The authoritative read-after-write recovery (QueryUserByEmailAuthoritative, OnboardingMiddleware.cs:306) shipped in PR #319, merged 2026-07-06 — three days before this issue. So this was most likely filed against a deployed image predating #319; current main has the fix.
Residual (Google-specific, actionable): Google has no preferred_username claim, so ObjectId = the numeric sub; all onboarding lookups instead key on Email. The recovery works only if the cookie's email exactly matches the stored content.email — an external/Google address whose casing differs (not normalized the way org UPNs are) would defeat the re-read and reproduce the exact loop, for external emails only. Fix (hardening, PR incoming): normalize email to a canonical trimmed/lower-cased form symmetrically at the write (content.email) and every read (FindUserByEmail, the Onboarding queries) so the post-onboarding lookup is casing-robust. NOT a retry/timer (that's the forbidden band-aid class — the authoritative re-read is the correct mechanism). Primary verification is redeploy main + confirm.
Correction to my earlier comment: I hypothesized a residual Google/external-email casing gap, but on tracing every onboarding write/read of content.email (and an empirical revert-check on current main) that gap does not reproduce — the lookup is case-insensitive on both backends:
Postgres: content.email is a text field → PostgreSqlSqlGenerator emits LOWER(content->>'email') = @p with @p = value.ToLowerInvariant() (PostgreSqlSqlGenerator.cs:1005-1008).
A probe storing Mixed.Case@Example.COM and looking up mixed.case@example.com via the exact FindUserByEmail query shape passed on main with zero code change. Also, the onboarding email field is read-only for any @-address (Onboarding.razor:157), so the stored and looked-up emails derive from the same cookie claim in one session (byte-identical anyway).
Net: there is no in-repo code fix needed. The primary bounce-loop was fixed by #319 (present on main); redeploy main and verify on the live portal clears any lingering stale-cache instances. No PR — shipping symmetric normalization would be a no-op that can't be revert-proven.
No code change required. The primary cause (stale synced-query cache bouncing the just-onboarded user back to /onboarding) was already fixed by #319's authoritative read-after-write, present on main — so this needs a redeploy of main + verify on the live portal to clear. The hypothesized Google/external-email casing residual was empirically disproved (the lookup is case-insensitive on both backends; a mixed-case probe resolves the user on main with zero code change — see the correction comment above). Closing as fixed-in-code; reopen if it still reproduces after a redeploy.
Activity
rbuergi commentedon Jul 18, 2026
RCA (verified against source)
Primary root cause — already fixed before this issue was filed. The submit path works (
Onboarding.razor:295 HandleSubmit→ the 4-query CombineLatest →UserOnboardingService.CreateUser/GrantSelfAdmin→OnOnboardingSucceededforced reload). The failure was that on the forced reloadOnboardingMiddleware.FindUserByEmail'sworkspace.GetQuery("auth:userByEmail:{email}")replays a permanently-cached EMPTY snapshot (pg_notify disabled on the partitioned portal →GetQueryis Replay(1) with no live delta), so the just-onboarded user is bounced back to/onboarding("button did nothing / cannot advance"). The authoritative read-after-write recovery (QueryUserByEmailAuthoritative,OnboardingMiddleware.cs:306) shipped in PR #319, merged 2026-07-06 — three days before this issue. So this was most likely filed against a deployed image predating #319; currentmainhas the fix.Residual (Google-specific, actionable): Google has no
preferred_usernameclaim, soObjectId= the numericsub; all onboarding lookups instead key on Email. The recovery works only if the cookie's email exactly matches the storedcontent.email— an external/Google address whose casing differs (not normalized the way org UPNs are) would defeat the re-read and reproduce the exact loop, for external emails only. Fix (hardening, PR incoming): normalize email to a canonical trimmed/lower-cased form symmetrically at the write (content.email) and every read (FindUserByEmail, the Onboarding queries) so the post-onboarding lookup is casing-robust. NOT a retry/timer (that's the forbidden band-aid class — the authoritative re-read is the correct mechanism). Primary verification is redeploymain+ confirm.rbuergi commentedon Jul 18, 2026
Correction to my earlier comment: I hypothesized a residual Google/external-email casing gap, but on tracing every onboarding write/read of
content.email(and an empirical revert-check on currentmain) that gap does not reproduce — the lookup is case-insensitive on both backends:content.emailis a text field →PostgreSqlSqlGeneratoremitsLOWER(content->>'email') = @pwith@p = value.ToLowerInvariant()(PostgreSqlSqlGenerator.cs:1005-1008).QueryEvaluator.CompareEqualusesOrdinalIgnoreCase(QueryEvaluator.cs:207).A probe storing
Mixed.Case@Example.COMand looking upmixed.case@example.comvia the exactFindUserByEmailquery shape passed onmainwith zero code change. Also, the onboarding email field is read-only for any@-address (Onboarding.razor:157), so the stored and looked-up emails derive from the same cookie claim in one session (byte-identical anyway).Net: there is no in-repo code fix needed. The primary bounce-loop was fixed by #319 (present on
main); redeploymainand verify on the live portal clears any lingering stale-cache instances. No PR — shipping symmetric normalization would be a no-op that can't be revert-proven.rbuergi commentedon Jul 18, 2026
No code change required. The primary cause (stale synced-query cache bouncing the just-onboarded user back to /onboarding) was already fixed by #319's authoritative read-after-write, present on main — so this needs a redeploy of main + verify on the live portal to clear. The hypothesized Google/external-email casing residual was empirically disproved (the lookup is case-insensitive on both backends; a mixed-case probe resolves the user on main with zero code change — see the correction comment above). Closing as fixed-in-code; reopen if it still reproduces after a redeploy.