Skip to content

Cache provider status and gate desktop startup#1962

Merged
juliusmarminge merged 13 commits intomainfrom
t3code/undeletable-thread-debug
Apr 13, 2026
Merged

Cache provider status and gate desktop startup#1962
juliusmarminge merged 13 commits intomainfrom
t3code/undeletable-thread-debug

Conversation

@juliusmarminge
Copy link
Copy Markdown
Member

@juliusmarminge juliusmarminge commented Apr 12, 2026

Summary

  • Cache provider status snapshots on disk and hydrate them on startup so the UI can show the last known state before the next probe finishes.
  • Delay desktop window creation until the backend is either listening or HTTP-ready, with a fallback path when packaged startup stalls.
  • Avoid probing provider health during registry initialization by seeding pending snapshots from current settings and refreshing lazily.
  • Add readiness detection, provider cache, and startup regression tests across desktop and server layers.

Testing

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test

Note

Medium Risk
Changes desktop startup sequencing and backend readiness detection, plus adds on-disk caching for provider status; regressions could delay window creation or show stale/incorrect provider states if cache/hydration logic is wrong.

Overview
Improves perceived startup readiness for both desktop and server provider status. Desktop now delays creating/showing the packaged main window until the backend is either detected as listening via log output or becomes HTTP-ready, instead of unconditionally polling a fixed endpoint.

Provider health snapshots (Codex/Claude) are now persisted to disk and rehydrated on startup, while managed providers start from a settings-derived pending snapshot and only probe on explicit refresh/streams. Websocket subscribeServerConfig now triggers a background refresh on subscription and debounces providerStatuses events (200ms), which also changes initial snapshots to potentially start with an empty providers list until refresh completes.

Reviewed by Cursor Bugbot for commit 46c5eed. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Cache provider status and gate desktop window creation on backend readiness

  • Provider snapshots are persisted to disk and restored on startup via a new cache in providerStatusCacheDir, so the UI can show last-known provider status without waiting for health probes.
  • Initial provider snapshots for Claude and Codex no longer trigger health checks; they report a pending state until an explicit refresh.
  • In packaged desktop builds, the first BrowserWindow is now created only after the backend signals readiness — either via a Listening on http:// log line (serverListeningDetector.ts) or HTTP probe — with a 60s timeout.
  • waitForHttpReady now accepts a configurable path and predicate instead of probing the hardcoded /api/auth/session endpoint.
  • The server emits a base welcome event immediately at startup; if auto-bootstrap is enabled, a second welcome with project/thread IDs follows once resolved.
  • Behavioral Change: windows start hidden and reveal on ready-to-show; packaged builds load the backend URL directly rather than going through resolveDesktopWindowUrl.

Macroscope summarized 46c5eed.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 12, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ccb70828-d02e-4a28-80d6-3fecd1b17322

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/undeletable-thread-debug

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Apr 12, 2026
Comment thread apps/desktop/src/main.ts
Comment thread apps/desktop/src/main.ts
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Non-atomic ref update causes concurrent provider data loss
    • Replaced the non-atomic Ref.get/Ref.set sequence in upsertProviders with a single atomic Ref.modify call that performs the read-merge-write in one step, preventing concurrent fibers from overwriting each other's updates.
  • ✅ Fixed: Options spread can override internal abort signal
    • Reversed the spread order in the object literal so that ...options comes before signal: controller.signal, ensuring the internal abort signal always takes precedence.

Create PR

Or push these changes by commenting:

@cursor push 1963e414b1
Preview (1963e414b1)
diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts
--- a/apps/desktop/src/main.ts
+++ b/apps/desktop/src/main.ts
@@ -365,8 +365,8 @@
 
   try {
     await waitForHttpReady(baseUrl, {
+      ...options,
       signal: controller.signal,
-      ...options,
     });
   } finally {
     if (backendReadinessAbortController === controller) {

diff --git a/apps/server/src/provider/Layers/ProviderRegistry.ts b/apps/server/src/provider/Layers/ProviderRegistry.ts
--- a/apps/server/src/provider/Layers/ProviderRegistry.ts
+++ b/apps/server/src/provider/Layers/ProviderRegistry.ts
@@ -108,17 +108,21 @@
         readonly publish?: boolean;
       },
     ) {
-      const previousProviders = yield* Ref.get(providersRef);
-      const mergedProviders = new Map(
-        previousProviders.map((provider) => [provider.provider, provider] as const),
-      );
+      const [previousProviders, providers] = yield* Ref.modify(
+        providersRef,
+        (previousProviders) => {
+          const mergedProviders = new Map(
+            previousProviders.map((provider) => [provider.provider, provider] as const),
+          );
 
-      for (const provider of nextProviders) {
-        mergedProviders.set(provider.provider, provider);
-      }
+          for (const provider of nextProviders) {
+            mergedProviders.set(provider.provider, provider);
+          }
 
-      const providers = orderProviderSnapshots([...mergedProviders.values()]);
-      yield* Ref.set(providersRef, providers);
+          const providers = orderProviderSnapshots([...mergedProviders.values()]);
+          return [[previousProviders, providers] as const, providers];
+        },
+      );
 
       if (haveProvidersChanged(previousProviders, providers)) {
         yield* Effect.forEach(nextProviders, persistProvider, {

You can send follow-ups to the cloud agent here.

Comment thread apps/server/src/provider/Layers/ProviderRegistry.ts
Comment thread apps/desktop/src/main.ts
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp bot commented Apr 12, 2026

Approvability

Verdict: Needs human review

This PR introduces significant behavioral changes to desktop startup gating and provider status caching. The changes affect when the main window appears and how provider status is initialized, with a noted race condition on first run where providers may appear empty until background refresh completes.

You can customize Macroscope's approvability policy. Learn more.

@juliusmarminge
Copy link
Copy Markdown
Member Author

@cursor push 1963e41

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Refresh with publish:false silently suppresses provider status updates
    • Removed { publish: false } from all syncProvider calls inside refresh, so the refresh path now publishes to changesPubSub when providers change, allowing WebSocket clients to receive updated provider statuses.

Create PR

Or push these changes by commenting:

@cursor push b58d42d5d6
Preview (b58d42d5d6)
diff --git a/apps/server/src/provider/Layers/ProviderRegistry.ts b/apps/server/src/provider/Layers/ProviderRegistry.ts
--- a/apps/server/src/provider/Layers/ProviderRegistry.ts
+++ b/apps/server/src/provider/Layers/ProviderRegistry.ts
@@ -150,20 +150,20 @@
       switch (provider) {
         case "codex":
           return yield* codexProvider.refresh.pipe(
-            Effect.flatMap((nextProvider) => syncProvider(nextProvider, { publish: false })),
+            Effect.flatMap((nextProvider) => syncProvider(nextProvider)),
           );
         case "claudeAgent":
           return yield* claudeProvider.refresh.pipe(
-            Effect.flatMap((nextProvider) => syncProvider(nextProvider, { publish: false })),
+            Effect.flatMap((nextProvider) => syncProvider(nextProvider)),
           );
         default:
           return yield* Effect.all(
             [
               codexProvider.refresh.pipe(
-                Effect.flatMap((nextProvider) => syncProvider(nextProvider, { publish: false })),
+                Effect.flatMap((nextProvider) => syncProvider(nextProvider)),
               ),
               claudeProvider.refresh.pipe(
-                Effect.flatMap((nextProvider) => syncProvider(nextProvider, { publish: false })),
+                Effect.flatMap((nextProvider) => syncProvider(nextProvider)),
               ),
             ],
             {

You can send follow-ups to the cloud agent here.

Comment thread apps/server/src/provider/Layers/ProviderRegistry.ts
@juliusmarminge
Copy link
Copy Markdown
Member Author

@cursor push b58d42d

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: No window created when backend readiness times out
    • Replaced the catch handler's log-only behavior with a call to handleFatalStartupError so the user sees an error dialog and the app quits gracefully instead of running invisibly.
  • ✅ Fixed: Primary environment skips config subscription and provider refresh
    • Added kind === "primary" to the config subscription condition so primary environments subscribe to subscribeServerConfig and trigger the provider refresh immediately.

Create PR

Or push these changes by commenting:

@cursor push c5c27df020
Preview (c5c27df020)
diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts
--- a/apps/desktop/src/main.ts
+++ b/apps/desktop/src/main.ts
@@ -448,10 +448,7 @@
       if (isBackendReadinessAborted(error)) {
         return;
       }
-      writeDesktopLogHeader(
-        `bootstrap backend readiness warning message=${formatErrorMessage(error)}`,
-      );
-      console.warn("[desktop] backend readiness check timed out during packaged bootstrap", error);
+      handleFatalStartupError("backend-readiness", error);
     })
     .finally(() => {
       if (backendInitialWindowOpenInFlight === nextOpen) {

diff --git a/apps/web/src/environments/runtime/connection.ts b/apps/web/src/environments/runtime/connection.ts
--- a/apps/web/src/environments/runtime/connection.ts
+++ b/apps/web/src/environments/runtime/connection.ts
@@ -264,7 +264,7 @@
   );
 
   const unsubConfig =
-    input.kind === "saved" || input.onConfigSnapshot
+    input.kind === "saved" || input.kind === "primary" || input.onConfigSnapshot
       ? input.client.server.subscribeConfig(
           (event: Parameters<Parameters<WsRpcClient["server"]["subscribeConfig"]>[0]>[0]) => {
             if (event.type !== "snapshot") {

You can send follow-ups to the cloud agent here.

Comment thread apps/desktop/src/main.ts
Comment thread apps/web/src/environments/runtime/connection.ts Outdated
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Duplicate pending provider factory functions across providers
    • Extracted a shared makePendingProviderSnapshot helper in providerSnapshot.ts parameterized by provider name, display name, built-in models, custom models, default capabilities, and enabled flag, then replaced both makePendingClaudeProvider and makePendingCodexProvider with one-liner calls to it.

Create PR

Or push these changes by commenting:

@cursor push 94bcdd9864
Preview (94bcdd9864)
diff --git a/apps/server/src/provider/Layers/ClaudeProvider.ts b/apps/server/src/provider/Layers/ClaudeProvider.ts
--- a/apps/server/src/provider/Layers/ClaudeProvider.ts
+++ b/apps/server/src/provider/Layers/ClaudeProvider.ts
@@ -21,6 +21,7 @@
   detailFromResult,
   extractAuthBoolean,
   isCommandMissingCause,
+  makePendingProviderSnapshot,
   parseGenericCliVersion,
   providerModelsFromSettings,
   spawnAndCollect,
@@ -663,45 +664,15 @@
   });
 });
 
-const makePendingClaudeProvider = (claudeSettings: ClaudeSettings): ServerProvider => {
-  const checkedAt = new Date().toISOString();
-  const models = providerModelsFromSettings(
-    BUILT_IN_MODELS,
-    PROVIDER,
-    claudeSettings.customModels,
-    DEFAULT_CLAUDE_MODEL_CAPABILITIES,
-  );
-
-  if (!claudeSettings.enabled) {
-    return buildServerProvider({
-      provider: PROVIDER,
-      enabled: false,
-      checkedAt,
-      models,
-      probe: {
-        installed: false,
-        version: null,
-        status: "warning",
-        auth: { status: "unknown" },
-        message: "Claude is disabled in T3 Code settings.",
-      },
-    });
-  }
-
-  return buildServerProvider({
+const makePendingClaudeProvider = (claudeSettings: ClaudeSettings): ServerProvider =>
+  makePendingProviderSnapshot({
     provider: PROVIDER,
-    enabled: true,
-    checkedAt,
-    models,
-    probe: {
-      installed: false,
-      version: null,
-      status: "warning",
-      auth: { status: "unknown" },
-      message: "Claude provider status has not been checked in this session yet.",
-    },
+    displayName: "Claude",
+    builtInModels: BUILT_IN_MODELS,
+    customModels: claudeSettings.customModels,
+    defaultCapabilities: DEFAULT_CLAUDE_MODEL_CAPABILITIES,
+    enabled: claudeSettings.enabled,
   });
-};
 
 export const ClaudeProviderLive = Layer.effect(
   ClaudeProvider,

diff --git a/apps/server/src/provider/Layers/CodexProvider.ts b/apps/server/src/provider/Layers/CodexProvider.ts
--- a/apps/server/src/provider/Layers/CodexProvider.ts
+++ b/apps/server/src/provider/Layers/CodexProvider.ts
@@ -28,6 +28,7 @@
   detailFromResult,
   extractAuthBoolean,
   isCommandMissingCause,
+  makePendingProviderSnapshot,
   parseGenericCliVersion,
   providerModelsFromSettings,
   spawnAndCollect,
@@ -552,45 +553,15 @@
   });
 });
 
-const makePendingCodexProvider = (codexSettings: CodexSettings): ServerProvider => {
-  const checkedAt = new Date().toISOString();
-  const models = providerModelsFromSettings(
-    BUILT_IN_MODELS,
-    PROVIDER,
-    codexSettings.customModels,
-    DEFAULT_CODEX_MODEL_CAPABILITIES,
-  );
-
-  if (!codexSettings.enabled) {
-    return buildServerProvider({
-      provider: PROVIDER,
-      enabled: false,
-      checkedAt,
-      models,
-      probe: {
-        installed: false,
-        version: null,
-        status: "warning",
-        auth: { status: "unknown" },
-        message: "Codex is disabled in T3 Code settings.",
-      },
-    });
-  }
-
-  return buildServerProvider({
+const makePendingCodexProvider = (codexSettings: CodexSettings): ServerProvider =>
+  makePendingProviderSnapshot({
     provider: PROVIDER,
-    enabled: true,
-    checkedAt,
-    models,
-    probe: {
-      installed: false,
-      version: null,
-      status: "warning",
-      auth: { status: "unknown" },
-      message: "Codex provider status has not been checked in this session yet.",
-    },
+    displayName: "Codex",
+    builtInModels: BUILT_IN_MODELS,
+    customModels: codexSettings.customModels,
+    defaultCapabilities: DEFAULT_CODEX_MODEL_CAPABILITIES,
+    enabled: codexSettings.enabled,
   });
-};
 
 export const CodexProviderLive = Layer.effect(
   CodexProvider,

diff --git a/apps/server/src/provider/providerSnapshot.ts b/apps/server/src/provider/providerSnapshot.ts
--- a/apps/server/src/provider/providerSnapshot.ts
+++ b/apps/server/src/provider/providerSnapshot.ts
@@ -151,6 +151,53 @@
   };
 }
 
+export function makePendingProviderSnapshot(input: {
+  provider: ServerProvider["provider"];
+  displayName: string;
+  builtInModels: ReadonlyArray<ServerProviderModel>;
+  customModels: ReadonlyArray<string>;
+  defaultCapabilities: ModelCapabilities;
+  enabled: boolean;
+}): ServerProvider {
+  const checkedAt = new Date().toISOString();
+  const models = providerModelsFromSettings(
+    input.builtInModels,
+    input.provider,
+    input.customModels,
+    input.defaultCapabilities,
+  );
+
+  if (!input.enabled) {
+    return buildServerProvider({
+      provider: input.provider,
+      enabled: false,
+      checkedAt,
+      models,
+      probe: {
+        installed: false,
+        version: null,
+        status: "warning",
+        auth: { status: "unknown" },
+        message: `${input.displayName} is disabled in T3 Code settings.`,
+      },
+    });
+  }
+
+  return buildServerProvider({
+    provider: input.provider,
+    enabled: true,
+    checkedAt,
+    models,
+    probe: {
+      installed: false,
+      version: null,
+      status: "warning",
+      auth: { status: "unknown" },
+      message: `${input.displayName} provider status has not been checked in this session yet.`,
+    },
+  });
+}
+
 export const collectStreamAsString = <E>(
   stream: Stream.Stream<Uint8Array, E>,
 ): Effect.Effect<string, E> =>

You can send follow-ups to the cloud agent here.

message: "Claude provider status has not been checked in this session yet.",
},
});
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate pending provider factory functions across providers

Low Severity

makePendingClaudeProvider and makePendingCodexProvider have nearly identical structure: compute checkedAt, derive models from settings, and return a buildServerProvider call with the same shape — differing only in the provider constant, settings type, built-in models, capabilities, and message strings. This duplication could be extracted into a shared helper parameterized by those values.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7f6ff53. Configure here.

Comment thread apps/web/src/components/ChatView.tsx Outdated
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Debug console.log statements left in production files
    • Removed all three debug console.log statements from index.html (line 12) and main.tsx (lines 1 and 32) that were logging startup timestamps to the browser console.

Create PR

Or push these changes by commenting:

@cursor push dc8cb2f5b3
Preview (dc8cb2f5b3)
diff --git a/apps/web/index.html b/apps/web/index.html
--- a/apps/web/index.html
+++ b/apps/web/index.html
@@ -9,9 +9,6 @@
     <link rel="icon" href="/favicon.ico" sizes="48x48" />
     <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
     <script>
-      console.log("index.html loaded at", new Date().toISOString());
-    </script>
-    <script>
       (() => {
         const LIGHT_BACKGROUND = "#ffffff";
         const DARK_BACKGROUND = "#161616";

diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx
--- a/apps/web/src/main.tsx
+++ b/apps/web/src/main.tsx
@@ -1,5 +1,3 @@
-console.log("main.tsx loaded at", new Date().toISOString());
-
 import React from "react";
 import ReactDOM from "react-dom/client";
 import { RouterProvider } from "@tanstack/react-router";
@@ -28,5 +26,3 @@
     <RouterProvider router={router} />
   </React.StrictMode>,
 );
-
-console.log("main.tsx rendered at", new Date().toISOString());

You can send follow-ups to the cloud agent here.

Comment thread apps/web/src/main.tsx Outdated
juliusmarminge and others added 6 commits April 13, 2026 14:00
- Persist provider snapshots between sessions
- Open the packaged desktop window when the backend is listening or HTTP-ready
- ProviderRegistry: Use Ref.modify for atomic read-modify-write in
  upsertProviders to prevent concurrent provider data loss when
  multiple fibers update providersRef simultaneously.

- Desktop main: Reorder spread in waitForBackendHttpReady so the
  internal abort controller signal always takes precedence over
  any signal passed via options.

Applied via @cursor push command
- Keep the desktop window hidden until it is ready to show
- Log static client requests with request type, source, and duration
- Mirror backend output to console in development
Co-authored-by: codex <codex@users.noreply.github.com>
@juliusmarminge juliusmarminge force-pushed the t3code/undeletable-thread-debug branch from f6ca1f1 to a0b1d06 Compare April 13, 2026 21:10
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 4 total unresolved issues (including 2 from previous reviews).

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Measurement script checks for HTML that no longer exists
    • Changed the check from body.includes('<div id="root"></div>') to body.includes('<div id="root">') so it matches the root div even when it contains splash screen content.
  • ✅ Fixed: Console.log timestamps committed to production HTML and entry
    • Removed all three console.log timestamp calls from index.html and main.tsx that were debug instrumentation not intended for production.

Create PR

Or push these changes by commenting:

@cursor push e62c107eef
Preview (e62c107eef)
diff --git a/apps/web/index.html b/apps/web/index.html
--- a/apps/web/index.html
+++ b/apps/web/index.html
@@ -9,9 +9,6 @@
     <link rel="icon" href="/favicon.ico" sizes="48x48" />
     <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
     <script>
-      console.log("index.html loaded at", new Date().toISOString());
-    </script>
-    <script>
       (() => {
         const LIGHT_BACKGROUND = "#ffffff";
         const DARK_BACKGROUND = "#161616";

diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx
--- a/apps/web/src/main.tsx
+++ b/apps/web/src/main.tsx
@@ -1,5 +1,3 @@
-console.log("main.tsx loaded at", new Date().toISOString());
-
 import React from "react";
 import ReactDOM from "react-dom/client";
 import { RouterProvider } from "@tanstack/react-router";
@@ -28,5 +26,3 @@
     <RouterProvider router={router} />
   </React.StrictMode>,
 );
-
-console.log("main.tsx rendered at", new Date().toISOString());

diff --git a/scripts/measure-react-startup.ts b/scripts/measure-react-startup.ts
--- a/scripts/measure-react-startup.ts
+++ b/scripts/measure-react-startup.ts
@@ -159,7 +159,7 @@
       const body = await response.text();
       if (
         response.ok &&
-        body.includes('<div id="root"></div>') &&
+        body.includes('<div id="root">') &&
         body.includes('<script type="module"')
       ) {
         return {

You can send follow-ups to the cloud agent here.

Comment thread scripts/measure-react-startup.ts Outdated
Comment thread apps/web/index.html Outdated
@github-actions github-actions bot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Apr 13, 2026
Comment on lines +91 to +92
);
const providersRef = yield* Ref.make<ReadonlyArray<ServerProvider>>(cachedProviders);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low Layers/ProviderRegistry.ts:91

On first run without cache files, getProviders returns an empty array because cachedProviders filters out all undefined values from missing cache files, and fallbackProviders is never used to seed the initial state. This creates a race where callers receive empty results until the background stream handlers emit provider updates. Consider initializing providersRef with fallbackProviders when no cached providers exist.

    const providersRef = yield* Ref.make<ReadonlyArray<ServerProvider>>(cachedProviders);
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/ProviderRegistry.ts around lines 91-92:

On first run without cache files, `getProviders` returns an empty array because `cachedProviders` filters out all `undefined` values from missing cache files, and `fallbackProviders` is never used to seed the initial state. This creates a race where callers receive empty results until the background stream handlers emit provider updates. Consider initializing `providersRef` with `fallbackProviders` when no cached providers exist.

Evidence trail:
apps/server/src/provider/Layers/ProviderRegistry.ts lines 50-83 (fallbackProviders loaded but only used in hydrateCachedProvider when cache exists), lines 72-75 (returns undefined when cachedProvider is undefined instead of using fallbackProvider), lines 78-80 (filters out undefined values), line 83 (providersRef initialized with potentially empty cachedProviders), line 151 (getProviders returns Ref.get(providersRef))

apps/server/src/provider/providerStatusCache.ts lines 54-58 (readProviderStatusCache returns undefined when file doesn't exist)

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Activate handler silently no-ops in development mode
    • Added an explicit isDevelopment check in the activate handler that calls createWindow() directly, bypassing ensureInitialBackendWindowOpen() which has an early return guard for development mode.

Create PR

Or push these changes by commenting:

@cursor push 28c13607db
Preview (28c13607db)
diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts
--- a/apps/desktop/src/main.ts
+++ b/apps/desktop/src/main.ts
@@ -2022,6 +2022,10 @@
         revealWindow(existingWindow);
         return;
       }
+      if (isDevelopment) {
+        mainWindow = createWindow();
+        return;
+      }
       ensureInitialBackendWindowOpen();
     });
   })

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit a2f6ad2. Configure here.

Comment thread apps/desktop/src/main.ts
@juliusmarminge
Copy link
Copy Markdown
Member Author

@cursor push 28c1360

ensureInitialBackendWindowOpen() has an early return when isDevelopment is true,
which is correct for the initial bootstrap path but prevents window creation when
the macOS dock icon is clicked after all windows are closed. Handle development
mode explicitly in the activate handler by calling createWindow() directly.

Applied via @cursor push command
@juliusmarminge juliusmarminge merged commit 008ac5c into main Apr 13, 2026
12 checks passed
@juliusmarminge juliusmarminge deleted the t3code/undeletable-thread-debug branch April 13, 2026 22:29
smraikai pushed a commit to smraikai/t3code that referenced this pull request Apr 16, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
aaditagrawal added a commit to aaditagrawal/t3code that referenced this pull request Apr 16, 2026
Integrates upstream/main (d22c6f5) into the fork while preserving all
multi-provider support (codex, claudeAgent, copilot, cursor, opencode,
geminiCli, amp, kilo) and fork UI/UX additions.

Highlights adopted from upstream:
- Nightly release channel + update channel selector (pingdotgg#2012, pingdotgg#2049, pingdotgg#1969)
- Filesystem browse API + command palette project picker (pingdotgg#2024)
- Launch Args setting for Claude provider (pingdotgg#1971)
- Kiro editor support in open picker (pingdotgg#1974)
- Claude plan events for TodoWrite during input streaming (pingdotgg#1541)
- Lost provider session recovery (pingdotgg#1938)
- Cache provider status and gate desktop startup (pingdotgg#1962)
- LegendList migration for chat scrolling and branch lists (pingdotgg#1953)
- Shell snapshot queries + backfill migration (pingdotgg#1973, pingdotgg#2004)
- PATH hydration + fallback detection (pingdotgg#1799)
- Warm sidebar thread subscriptions (pingdotgg#2001)
- Full thread title tooltip (pingdotgg#1994)
- Markdown file link UX (pingdotgg#1956), composer polish (pingdotgg#1944, pingdotgg#1992, pingdotgg#1985)
- Worktree/branch state + draft reuse fixes (pingdotgg#2005, pingdotgg#2003, pingdotgg#1995, pingdotgg#1936)
- Window controls overlay for Windows/Linux (pingdotgg#1969)
- Backend readiness timeout 10s→30s (pingdotgg#1979)
- Clear tracked RPCs on reconnect, live stream subscriptions (pingdotgg#2000, pingdotgg#1972)
- Various misc fixes (pingdotgg#2051, pingdotgg#2052, pingdotgg#2025, pingdotgg#2027, pingdotgg#2049, pingdotgg#1997, pingdotgg#1975)

Fork features preserved and reconciled:
- All 8 provider adapters + conformance tests
- Extended ProviderKind union across contracts/model/settings/provider
- appearance/accentColor/themeConfig/ProviderLogo UI system
- customModels + gitTextGeneration + providerModelOptions
- Migration IDs 23 (NormalizeLegacyProviderKinds) and 24
  (RepairProjectionThreadProposedPlanImplementationColumns) kept; new
  upstream migrations registered at IDs 25-26 to avoid breaking deployed
  fork databases
- DesktopBridge: log directory channels (LOG_DIR/LIST/READ/OPEN_DIR)
  retained; getWsUrl replaced by upstream's getAppBranding
- PROVIDER_CACHE_IDS extended to all 8 providers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants