Skip to main content

App loading overlay

[← Source sessions](/audits/fable/key-sessions) · [Transcript archive](/audits/fable/transcripts) · [Executive summary](/audits/fable/findings-summary)
# App loading overlay | Field | Value | | --- | --- | | Session ID | `f2b45214-e1b0-4d22-85f9-8a7f49492e69` | | Record type | Lead review session export | :::note[Appendix record] Readable export of a supplementary review session. Tool outputs and system context are omitted; download the [JSONL archive](/audits/fable-chats-4626-2026-06.zip) for the complete log. ::: ## Engagement brief

Friday, Jun 12, 2026, 12:56 AM (UTC-7) consoleNoisePatch.ts:33 [app-loading] overlay still active after 8s — held by: waitlist-auth-bootstrap (anonymous) @ consoleNoisePatch.ts:33 main.tsx:235 [vite] hot updated: /src/pages/Swap.tsx main.tsx:235 [vite] hot updated: /src/index.css main.tsx:235 [vite] hot updated: /src/pages/Swap.tsx main.tsx:235 [vite] hot updated: /src/index.css main.tsx:235 [vite] hot updated: /src/pages/Swap.tsx main.tsx:235 [vite] hot updated: /src/index.css main.tsx:231 [worker] client received event data Object client worker received event data [object Object] main.tsx:231 [worker] client received event data Object client worker received event data [object Object] main.tsx:231 [worker] client received event data Object main.tsx:231 [worker] client received event data Object client.ts:100 client worker received event data Object main.tsx:231 [worker] client received event data Object waitlist:1 Access to fetch at 'https://privy.4626.fun/api/v1/analytics_events' from origin 'http://localhost:5174' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. main.tsx:231 Unable to submit event. This is not an issue. 1112-e0b24202c26e4679.js?dpl=dpl_3sAG3aQsxrbvq87A9Ci619WRcBTZ:1 Successfully verified access token. consoleNoisePatch.ts:33 [xmtp] Client.create registration with SCW signer failed; trying next signer if available… An unknown RPC error occurred.

Details: Missing auth token. Version: viem@2.45.1 (anonymous) @ consoleNoisePatch.ts:33 bindings_wasm.js:3013 ERROR xmtp_db::encrypted_store::database::wasm: error code=NoModificationAllowedError, Failed to execute 'createSyncAccessHandle' on 'FileSystemFileHandle': Access Handles cannot be created if there is another open Access Handle or Writable stream associated with the same file.:7

(anonymous) @ bindings_wasm.js:3013 bindings_wasm.js:3013 ERROR xmtp_db::encrypted_store::database::wasm: An error occurred while creating sync access handle

(anonymous) @ bindings_wasm.js:3013 main.tsx:231 [worker] client received event data Object client.ts:100 client worker received event data Object main.tsx:231 [worker] client received event data Object 1112-e0b24202c26e4679.js?dpl=dpl_3sAG3aQsxrbvq87A9Ci619WRcBTZ:1 Successfully verified access token. waitlist:1 Access to fetch at 'https://privy.4626.fun/api/v1/analytics_events' from origin 'http://localhost:5174' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. main.tsx:231 Unable to submit event. This is not an issue. consoleNoisePatch.ts:33 [xmtp] Client.create registration with EOA signer failed; trying next signer if available… An unknown RPC error occurred.

Details: Missing auth token. Version: viem@2.45.1 (anonymous) @ consoleNoisePatch.ts:33 consoleNoisePatch.ts:37 [xmtp] connect error: UnknownRpcError: An unknown RPC error occurred.

Details: Missing auth token. Version: viem@2.45.1 at withRetry.delay.count.count (buildRequest.ts:243:25) at async attemptRetry (withRetry.ts:44:22)Caused by: ei3: Missing auth token. at index-C47JxA4c.mjs:172:8385 at n24 (index-C47JxA4c.mjs:172:10856) (anonymous) @ consoleNoisePatch.ts:37 consoleNoisePatch.ts:37 Cannot update a component (WaitlistGroupChatSurface) while rendering a different component (Hydrate). To locate the bad setState() call inside Hydrate, follow the stack trace as described in https://react.dev/link/setstate-in-render

Analysis

I'll dig into the waitlist group chat / XMTP wiring to diagnose these errors — the setState-during-Hydrate warning and the "Missing auth token" XMTP signer failures look related to how the waitlist chat surface mounts.

Looking at the console output from the dev server, there are several issues blocking the waitlist page: the app-loading overlay is stuck after 8 seconds waiting for the auth bootstrap to complete, XMTP client creation is failing with missing auth token errors across both smart contract and EOA signers, and there's an OPFS sync access handle conflict. The Privy analytics CORS errors are expected and not the blocker here.

The React warning about setState during render in WaitlistGroupChatSurface while Hydrate is rendering points to the known pattern from the docs — the waitlist group chat panel is mounting XMTP and wagmi consumers without deferring them until after the commit phase. The missing auth token in XMTP suggests the auto-connect is firing before the Privy session is ready, likely triggered when the marketing route loads. Let me dig into the relevant code to trace where these are happening.

Tools invoked: Grep, Grep

Analysis

Tools invoked: Read, Read

Analysis

Looking at the console errors, I'm seeing authentication issues with the Privy embedded wallet provider—the signer flow is failing because the Privy session is missing an auth token, which is causing both the SCW and EOA signer attempts to fail with "Missing auth token" errors. This suggests the localhost dev session might not have the proper authentication set up.

Beyond that, there's the OPFS lock contention from multiple XMTP workers, and more importantly, a React warning about state updates during the wagmi Hydrate render phase in WaitlistGroupChatSurface. The bootstrap overlay is also stuck for about 8 seconds, which points to the wagmi hydration not completing cleanly. The fix here is likely to defer the wagmi consumers one commit using useDeferUntilAfterCommit() in the WaitlistGroupChatPanel wrapper, since it's wrapping children in both AccountContextProvider and XmtpChatProvider.I've reviewed the chat surface and panel. Now I'll examine the hooks that interact with wagmi/Privy state to find the source of the hydration warnings and signer failures.

Tools invoked: Read, Read

Analysis

The issue is that WaitlistGroupChatSurface is triggering state updates while wagmi's Hydrate component is rendering, which violates React's rules. The chat surface depends on wagmi hooks like useAccount, useWalletClient, and useConnect through usePrepareWaitlistMessagingWallet, and when wagmi reconnects during hydration, it synchronously updates the store, causing the conflict. The fix is to defer these wagmi hook consumers until after the commit phase using useDeferUntilAfterCommit.

For the XMTP auth token error, the Privy embedded wallet is rejecting the personal_sign request because the Privy session isn't fully loaded or authenticated — the access token is missing. Even though the user clicked "Connect & join" and the client attempted to initialize, the signing fails at the Privy provider level, likely because the session state isn't ready or the iframe isn't properly initialized.

There's also an OPFS lock contention issue from another tab or worker, and the overlay is being held up by the waitlist component. I need to check whether the waitlist chat panel is wrapped inside the DeferWagmiConsumers component, since that's the mechanism that should prevent wagmi hook subscribers from updating during Hydrate's render phase.I found that the panel is using wagmi hooks directly, which is likely causing the hydration warning. Let me check how it relates to the existing DeferWagmiConsumers wrapper.

Tools invoked: Grep, Grep

Analysis

I need to check how WaitlistSetupWorkspace mounts the panel and verify that the waitlist route's wagmi providers properly wrap it—specifically looking for WalletProviders and whether it defers hook consumers as expected.

Tools invoked: Grep, Grep

Analysis

The chat panel is nested inside WalletProviders and DeferWagmiConsumers, which delays rendering by one commit after WagmiProvider mounts. But I'm seeing a warning that WaitlistGroupChatSurface is being updated while Hydrate is rendering — that shouldn't happen if the deferral is working. Let me check how useDeferUntilAfterCommit actually works; it looks like it uses a module-level shared store to track the first client commit.

The issue is that this store flips globally after the first commit, not per-mount. So if WalletProviders mounts later (after waitlist bootstrap), the store is already true, and DeferWagmiConsumers renders its children immediately in the same render pass as Hydrate. When Hydrate runs its rehydration logic during render, it notifies wagmi store subscribers that are already mounted — like WaitlistGroupChatSurface via useAccount — triggering setState during Hydrate's render. The HMR updates I'm seeing for Swap.tsx might be remounting Hydrate, which calls reconnect synchronously during render, causing subscribers to update while Hydrate is still rendering.

This matches the documented workspace pattern. Let me check useDeferUntilMounted to confirm the singleton behavior.The panel is already wrapped in WalletProviders with DeferWagmiConsumers. I'll check how the defer store works to see if late-mounted workspaces get defer access.

Tools invoked: Read

Analysis

So deferAfterCommitStore is a one-shot mechanism that locks in after the first commit, making DeferWagmiConsumers ineffective once the waitlist setup workspace mounts later. Since the children render in the same pass as wagmi's Hydrate, the hydration logic runs regardless of the reconnectOnMount setting.

The warning occurs because Hydrate calls hydrate() during its render phase, which synchronously updates the zustand store and notifies subscribers — but this only triggers the error if consumers are already mounted when Hydrate re-renders (like during HMR or state changes). On initial mount, the subscribers aren't set up yet since effects run after render, so the issue surfaces when Hydrate remounts while WaitlistGroupChatSurface is already subscribed and rendering.

Looking at the actual console output, there are several distinct issues: the overlay delay is just a bootstrap timing issue, the Privy analytics CORS is benign, the OPFS sync conflict is a known multi-tab problem, and the setState warning is a dev-mode artifact. But the real functional failure is the "Missing auth token" error from the embedded wallet RPC during XMTP Client creation — Privy's session appears partially restored (authenticated but token expired or unavailable to the iframe), so the signing requests fail even though the access token was verified earlier.

The root cause is likely a domain/origin mismatch: the custom auth domain privy.4626.fun doesn't allowlist localhost:5174, so the Privy iframe can't access the auth token when the XMTP signer tries to sign through the embedded wallet. The analytics CORS error is the visible symptom of this same origin restriction — if analytics calls are blocked, the wallet RPC calls probably are too, even though the initial token verification succeeded in a different context. I'm weighing whether to apply a fix or just report findings — the user pasted errors in agent mode, which usually signals they want solutions. The core issue is the Hydrate warning tied to WalletProviders mounting before React finishes its hydration pass. I could fix it by deferring the mount locally within each component using useState and useEffect rather than relying on the global store, which would be safer since it defers per instance. The risk is that other components like DeployVault also use the defer hook, but per-mount semantics is strictly safer — each mount just waits one macrotask before rendering its children. Let me think through whether changing the hook's behavior globally would break anything or if it's the right move here.

Now I'm tracing the actual warning sequence — WaitlistSetupWorkspace mounts, WalletProviders renders Hydrate, and the global store already committed so children render immediately. But the warning fires after XMTP errors and HMR updates, which suggests Hydrate might be re-rendering when WaitlistFlow state changes trigger re-renders up the tree. Each WaitlistFlow state change could cause Hydrate to re-render, and if children are updating during that render phase, that's where the warning comes from.

Looking at wagmi's Hydrate implementation, the issue is that hydrate() and its onMount callback run on every single render of Hydrate — they're not memoized. With reconnectOnMount={false} and storage configured, each Hydrate render synchronously calls setState to clear connections, which notifies zustand subscribers and triggers setState on mounted consumers like WaitlistGroupChatSurface during the render phase itself.

The async onMount function actually executes synchronously up to its first await — since config._internal.ssr is false, it skips the rehydrate await and goes straight to the synchronous setState call. This happens on every Hydrate re-render after consumers mount, causing the warning.

The real problem is that Hydrate re-renders whenever its parent re-renders because the children prop changes each time. The fix is to stabilize the component tree so Hydrate doesn't re-render unnecessarily — wrapping the children boundary in WalletProviders prevents the children prop from changing on every parent render.

The XMTP "Missing auth token" error is more critical though — it's coming from Privy's embedded wallet provider, which requires a valid access token to sign RPC calls. Even though the token verification succeeded in the logs, the wallet proxy or iframe call is missing the token somehow.

My hypothesis: the deploy-dry-run is running on port 5174, but the Privy app's allowed origins only include localhost:5173, not 5174. That would explain both the CORS error on analytics_events from privy.4626.fun and why the embedded wallet can't access the auth token — the origin mismatch blocks the cookie or token retrieval. I should check the frontend environment variables to see what origins are actually configured for Privy.Found the chat panel wiring in WalletProviders. Now I'll check the Privy origin config for the deploy-dry-run port since the CORS and auth token failures suggest an origin mismatch.

Tools invoked: Grep, Shell

Analysis

App-side env allows 5174. Now let me look at how the XMTP connect path builds its signers, and whether "Missing auth token" is a known failure mode.

The CORS error is coming from the Privy dashboard at privy.4626.fun not including the proper Access-Control-Allow-Origin header for http://localhost:5174, which suggests the Privy client configuration needs to be updated to recognize the localhost port in its allowlist.

The "Missing auth token" error is actually coming from within Privy's bundle when the XMTP signer makes a provider request — the token verification itself succeeded (logged twice), but something in the Privy embedded wallet provider is throwing this error when the XMTP signer tries to use it. I need to check how the XMTP signer code builds its signers and whether there's a known issue with "Missing auth token" in the repo.

Tools invoked: Grep, Grep

Analysis

The Swap page has a documented recovery pattern for exactly this Privy "Missing auth token" failure. Let me read it.

I'm recognizing a pattern with Privy's embedded wallet provider—when the "Missing auth token" error appears, it means the iframe session has gone stale. The key insight from the Swap page is that on localhost, third-party cookies for the custom auth domain don't work, so a simple token refresh won't fix it; instead, you need a full Privy logout followed by a fresh interactive login to reseed the iframe session properly.

Now I'm checking the Swap.tsx code around line 726 to see if there's already a recovery utility like refreshEmbeddedSignerSession or a retry pattern with session refresh that handles this—and whether the XMTP connect flow is missing similar recovery logic. I'm also noting the OPFS error about Access Handles is a known issue where another tab or worker is holding the database lock, so I need to look at how Swap handles this scenario.

Tools invoked: Read

Analysis

I'm checking how the waitlist chat error formatting handles missing auth tokens and whether there's a recovery hint available. Since the user shared console output without explicitly asking for changes, I should focus on diagnosing what they've shown rather than proposing improvements they didn't request.

Tools invoked: Grep

Analysis

Here's my read of that console dump. One real failure, the rest is known noise.

The real failure: stale Privy embedded-wallet session

The XMTP connect failed because the Privy embedded wallet refused to sign:

[xmtp] Client.create registration with SCW signer failed… Missing auth token.
[xmtp] Client.create registration with EOA signer failed… Missing auth token.

"Missing auth token" comes from inside Privy's embedded-wallet iframe, and the codebase already documents this exact mode on the Swap page:

  // ─── Stale embedded-signer session recovery ──────────────────────────────
// "Missing auth token" from the Privy embedded-wallet iframe cannot be fixed
// by page-side token refresh (custom auth-domain cookies are third-party on
// localhost). Only a real Privy logout + fresh interactive login re-seeds
// the iframe session.

On localhost:5174 the custom auth domain privy.4626.fun cookies are third-party, so the iframe session goes stale even while your page-level token still verifies (you can see "Successfully verified access token" succeeding right next to the failures). Fix: sign out of Privy on this dev origin and sign back in with email OTP, then retry "Connect & join waitlist chat". A page refresh or token refresh won't fix it.

Note the waitlist chat error formatter (waitlistChatErrors.ts) has no mapping for this error, unlike Swap which detects it and offers a one-click logout-and-reauth recovery (handleSigningSessionRecovery). If you want, I can wire the same detection + recovery CTA into the waitlist chat surface.

The rest, in order of appearance

  • overlay still active after 8s — held by: waitlist-auth-bootstrap — diagnostic only; the waitlist auth bootstrap took >8s on the dev server (likely slow API round-trips on the dry-run setup plus Privy init). Not an error by itself, just the watchdog flagging a slow hold.
  • Privy analytics CORS to privy.4626.fun/api/v1/analytics_events — benign. Privy itself logs "Unable to submit event. This is not an issue." It's only the analytics beacon being blocked for the localhost origin; it does not affect auth or signing.
  • OPFS createSyncAccessHandle / "another open Access Handle" — the XMTP local database is locked by another open handle, typically a second 4626.fun/localhost tab with chat mounted, or a same-tab XMTP worker that didn't release. The provider has handling for this (cross-tab release broadcast, retry with backoff); closing other chat tabs clears it. It compounds with the failed signer attempts above since each retry reopens the DB.
  • Cannot update WaitlistGroupChatSurface while rendering Hydrate — a dev-mode React warning, not a functional break. The waitlist setup workspace mounts its own WalletProviders late (after bootstrap), but DeferWagmiConsumers relies on useDeferUntilAfterCommit, whose backing store is a one-shot global — it's already true by the time this second wagmi tree mounts, so the defer is a no-op there. When wagmi's Hydrate re-renders with reconnectOnMount={false}, it synchronously resets connections in the store during render and notifies the already-mounted chat surface hooks. The fix would be making the defer per-mount (local state in DeferWagmiConsumers) instead of the global store.

So: the only thing actually blocking you is the stale Privy iframe session — sign out and back in on localhost:5174. Want me to apply either of the two code fixes (waitlist-chat recovery CTA for "Missing auth token", and/or the per-mount defer to silence the Hydrate warning)?

The user's console dump shows a missing auth token error that the waitlist chat formatter doesn't handle, so they're seeing a raw viem error instead of something user-friendly. I should give them the concrete fix (logout from Privy and redo the email OTP login on this dev origin) and mention the optional improvement of mapping that error with a recovery prompt like the Swap component does.