Skip to content

[Bug]: Sidebar V2 provider icon missing for non-primary (T3 Connect/SSH/Tailscale) environments #5289

Description

@ashraftown

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/web

Steps to reproduce

  1. Open the web app at app.t3.codes (no local/primary backend — connect only via T3 Connect to a remote host, or SSH/Tailscale to a remote host).
  2. Enable Sidebar V2.
  3. Create any thread and send at least one message.
  4. Look at the sidebar thread row — the provider icon (e.g. OpenCode/Codex) is missing.
  5. Confirm the remote host's server config reports providers correctly (providers array in the WebSocket snapshot message contains instanceId, driver, enabled: true for all configured providers, and the thread's modelSelection.instanceId matches one of them).

Expected behavior

The provider icon should render in Sidebar V2 thread rows for threads running on any connected environment, matching the icon shown in the model picker / composer.

Actual behavior

The icon is absent. The row's provider <span> (pointer-events-none ml-auto inline-flex shrink-0 items-center gap-1) is empty, and the hover tooltip also omits the provider line.

Impact

Minor bug or occasional failure

Root cause

apps/web/src/components/SidebarV2.tsx (default SidebarV2 component) builds providerEntryByInstanceId from primaryServerProvidersAtom:

const serverProviders = useAtomValue(primaryServerProvidersAtom);
const providerEntryByInstanceId = useMemo(
  () => new Map(
    deriveProviderInstanceEntries(serverProviders).map(
      (entry) => [entry.instanceId as string, entry] as const,
    ),
  ),
  [serverProviders],
);

primaryServerProvidersAtom only contains providers from the PrimaryConnectionTarget environment. On app.t3.codes with no local backend there is no primary target, so the list is empty and no icons render. On the desktop app it appears to work only because the local primary server exposes default provider instances whose instanceIds happen to match threads running on the remote environment.

This affects every non-primary connection type: RelayConnectionTarget (T3 Connect), BearerConnectionTarget (pairing URL/WSL), and SshConnectionTarget (SSH/Tailscale).

Proposed fix

Read providers from all connected environments via the already-imported environmentServerConfigsAtom instead of primaryServerProvidersAtom:

-  const serverProviders = useAtomValue(primaryServerProvidersAtom);
+  const serverConfigs = useAtomValue(environmentServerConfigsAtom);
   const providerEntryByInstanceId = useMemo(
-    () => new Map(
-      deriveProviderInstanceEntries(serverProviders).map(
-        (entry) => [entry.instanceId as string, entry] as const,
-      ),
-    ),
-    [serverProviders],
+    () => {
+      const map = new Map<string, ProviderInstanceEntry>();
+      for (const [, config] of serverConfigs) {
+        for (const entry of deriveProviderInstanceEntries(config.providers)) {
+          map.set(entry.instanceId as string, entry);
+        }
+      }
+      return map;
+    },
+    [serverConfigs],
   );

Reference implementation: ashraftown/t3code@a45c6ddc7 (branch fix/provider-icon-per-env).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions