Skip to content

Race Condition - 401 Errors on Startup in Electron Mode #333

@RayFernando1337

Description

@RayFernando1337

Bug Description

In Electron mode, multiple 401 (Unauthorized) errors occur on app startup because API requests are made before initApiKey() completes fetching the API key from the Electron main process.

Reproduction Steps

  1. Run npm run dev:electron (or OPEN_DEVTOOLS=true npm run dev:electron)
  2. Open DevTools console
  3. Observe 401 errors for /api/auth/token, /api/settings/status, /api/running-agents
  4. WebSocket connection also fails with "HTTP Authentication failed"

Expected Behavior

All API requests should include the API key header and succeed without 401 errors.

Actual Behavior

GET /api/auth/token 401
GET /api/settings/status 401
GET /api/running-agents 401
WebSocket connection failed: HTTP Authentication failed; no valid credentials available
[HTTP Client] Using API key from Electron  // This appears AFTER the 401s

Root Cause

initApiKey() is called inside a useEffect in RootLayoutContent, but other hooks (useSettingsMigration, useRunningAgents) call getHttpApiClient() which makes API requests before initApiKey() has completed.

The HttpApiClient constructor calls connectWebSocket() which calls fetchWsToken() - all before cachedApiKey is populated.

Affected Files

  • apps/ui/src/routes/__root.tsx (lines 82-120) - initApiKey() called in useEffect
  • apps/ui/src/lib/http-api-client.ts (lines 75-95) - initApiKey() async function
  • apps/ui/src/lib/http-api-client.ts (lines 280-300) - connectWebSocket() uses getApiKey() before it's ready
  • apps/ui/src/hooks/use-settings-migration.ts (line 102) - calls getHttpApiClient() early

Suggested Fix

Ensure initApiKey() completes before any component renders or makes API calls.

Option A: Block rendering until auth ready

// In __root.tsx or a wrapper component
const [authReady, setAuthReady] = useState(false);

useEffect(() => {
  initApiKey().then(() => setAuthReady(true));
}, []);

if (!authReady) return <LoadingSpinner />;

Option B: Lazy initialization in getHttpApiClient

// In http-api-client.ts
let clientInstance: HttpApiClient | null = null;
let initPromise: Promise<void> | null = null;

export const getHttpApiClient = async (): Promise<HttpApiClient> => {
  if (!initPromise) {
    initPromise = initApiKey();
  }
  await initPromise;
  
  if (!clientInstance) {
    clientInstance = new HttpApiClient();
  }
  return clientInstance;
};

Regression

Introduced in PR #321 (protect-api-with-api-key), merged Dec 30, 2025.

Key commits:

  • d68de99 - Added auth requirement to API endpoints
  • 469ee5f - Added fetchWsToken() and hardened auth
  • d66259b - Added verifySession() and modified auth flow

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