Skip to content

[Bug]: V2 desktop exits on every Windows launch: DesktopClerkBridgeInitializationError (registerSchemesAsPrivileged after ready), since #12480 #13195

Description

@astarktc

What happened

A packaged Windows x64 build of the Orchestrator V2 branch (#2829) exits with code 1 on every launch, before any window appears. It fails the same way on two machines (Windows 11 26200 and Windows 10 19045), on a fresh profile and on relaunch. The main process logs one error and quits:

ERROR (#4): DesktopClerkBridgeInitializationError: Failed to initialize the desktop Clerk bridge for state directory "C:\Users\<user>\.t3\userdata" (development: false).
  [cause]: Error: protocol.registerSchemesAsPrivileged should be called before app is ready
      at createClerkBridge (...\resources\app.asar\apps\desktop\dist-electron\main.cjs:118914:22)
      at createDesktopClerkBridge (...\main.cjs:128379:9)

The same commit built for macOS launches normally.

Diagnosis

This is a Windows-only ordering race in desktop startup, and #12480 introduced it.

  1. createClerkBridge (@clerk/electron) calls protocol.registerSchemesAsPrivileged, which must run before Electron's ready. main.ts:220 already notes the risk: "Acquire strict pre-ready setup before Clerk, whose userData resolution can yield and let Electron emit ready."
  2. DesktopClerk.make (apps/desktop/src/app/DesktopClerk.ts:92) first awaits DesktopUserData.resolveUserDataPath(environment) and only then creates the bridge.
  3. On non-win32, resolveUserDataPath returns at DesktopUserData.ts:60 (if (input.platform !== "win32") return destinationPath;) without touching the disk, so it never yields and macOS/Linux are unaffected.
  4. On win32 it goes on to inspect() (DesktopUserData.ts:48) the profile Local State files through the Effect Node FileSystem. fs.exists is async, so the fiber yields to the event loop at least once, and Electron emits ready during that yield. When createClerkBridge then runs, registerSchemesAsPrivileged throws, and DesktopClerkBridgeInitializationError ends startup.

Every Windows launch takes that path, because the first inspect happens regardless of profile state. In our runs it failed deterministically (2 of 2 machines, every launch). Verified against the current #2829 head 060756de5a: DesktopUserData.ts, DesktopClerk.ts and main.ts are unchanged there.

Steps to reproduce

  1. On Windows x64, check out the feat(orchestrator): introduce new orchestrator #2829 head (verified at 060756de5a, also reproduced at 2341c5a680).
  2. pnpm install && pnpm dist:desktop:win:x64.
  3. Install release\T3-Code-0.0.42-x64.exe (/S works) and launch T3 Code (Alpha).exe. The process exits within about a second, and running it with --enable-logging=stderr prints the error above.

Version

0.0.42 (Orchestrator V2 branch #2829, head 060756d; also 2341c5a)

Environment

Windows 11 Pro 26200 x64 and Windows 10 19045 x64; Electron 44.4.2; unsigned packaged NSIS build (dist:desktop:win:x64); Node 26.4, pnpm 11.10.0

Evidence

- Fresh `%USERPROFILE%\.t3\userdata` with only `desktop-settings.json`. There is no `server.trace.ndjson` because the backend never starts. Scheduled-task launches returned `LastTaskResult 1`.
- The single stdout line from the main process is the stack above (fiber #4, the first evaluation). This is not the double-evaluation path from #11720.
- With only the fix below applied, both machines launch normally, the backend binds :3773, and `/.well-known/t3/environment` returns 200 (serverVersion 0.0.42, orchestrationProtocolVersion 2). The rest of the app is unchanged.

Related issues

#12480 (introduced the win32 userData migration), #11720 (same error string, different cause: double evaluation of main.cjs on Linux)

Fix applied or workaround

Local fix (fork): in DesktopClerk.make, run resolveUserDataPath against a synchronous FileSystem, so userData resolution can no longer yield before the bridge exists. The resolver and its tests stay untouched:

const userDataPath = yield* DesktopUserData.resolveUserDataPath(environment).pipe(
  Effect.provideService(FileSystem.FileSystem, preReadySyncFileSystem),
);

preReadySyncFileSystem is FileSystem.makeNoop({ exists, readFileString, makeDirectory, writeFileString }), implemented with node:fs existsSync/readFileSync/mkdirSync/writeFileSync (errno mapped to PlatformError.systemError, so the wx/AlreadyExists path still works). tsc is clean and the desktop src/app tests pass (76/76). Commit: astarktc@ea60f40f7a

An alternative that avoids sync I/O is to create the bridge before resolving userData. That only works if the bridge's single-instance lock doesn't depend on app.setPath("userData"), which the current comment says it does.

Filed by

Pi (claude-opus-5-5) in a T3 Code thread, operator-reviewed

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions