Cowork (Local Agent Mode) fails when Cloudflare blocks net.fetch bootstrap API — "Could not determine session storage dir"

Resolved 💬 3 comments Opened Feb 8, 2026 by yu-pxn Closed Feb 12, 2026

Environment

  • App version: 1.1.2321
  • OS: macOS 26.2.0 (Tahoe) / Darwin 25.2.0
  • Arch: arm64 (Apple M1 Max)

Description

Cowork sessions consistently fail with Could not determine session storage dir at the skills_sync step. Chat and Claude Code (CLI) work perfectly — only the Local Agent Mode / Cowork feature is affected.

Root cause: Our corporate network egress is centrally managed for security reasons, which causes our IP to be flagged by Cloudflare's managed challenge. The Electron main process uses net.fetch (Chromium's network stack in non-browser context) to call /api/bootstrap, which cannot complete Cloudflare's JavaScript challenge. The webview renderer can pass the challenge (so chat works), but the main process net.fetch cannot.

Detailed Analysis

Failure Chain
  1. nu() (bootstrap function) calls Ae.net.fetch(\${Xr()}/api/bootstrap\) from the main process
  2. Cloudflare returns 403 Managed Challenge (HTML page with JS challenge, not JSON)
  3. .json() parse fails → catch block returns null
  4. doInitialize() sets this.currentAccountId = null
  5. getAccountStorageDir() returns null (because !this.currentAccountId)
  6. getSessionStorageDir() returns null
  7. getOutputsDir() / getClaudeConfigDir() throws "Could not determine session storage dir"
Evidence from logs
[warn] [growthbook] API returned status 403
[error] Failed to fetch blocklist: Error: Blocklist API returned status 403
[error] Failed to check allowlist status: Error: Organization API returned status 403
[info] claude.ai account active and logged in          ← webview session is fine
[info] Starting local session local_xxx in /home/xxx
[error] Session initialization failed: Could not determine session storage dir

ALL net.fetch calls to claude.ai return 403 with Cloudflare challenge HTML. Meanwhile, the webview (renderer process) successfully authenticates and displays the chat interface normally.

Why Chat works but Cowork doesn't

| Component | Network Method | Cloudflare Challenge | Result |
|-----------|---------------|---------------------|--------|
| Chat (webview) | Renderer process (full browser context) | Can execute JS challenge | Works |
| Claude Code (CLI) | OAuth token + API key auth | Different endpoint / auth flow | Works |
| Cowork bootstrap | Main process net.fetch | Cannot execute JS challenge | Fails |

Suggested Fix

The account UUID and org ID are already available in the Electron app from the webview session (Local Storage, cookies). The bootstrap API call in the main process is redundant when the user is already authenticated in the webview.

Option A: Fall back to webview session data when net.fetch to /api/bootstrap fails:

async function nu() {
  return O4 || np || (np = (async () => {
    try {
      const e = await (await Ae.net.fetch(\`\${Xr()}/api/bootstrap\`)).json();
      return e.account ? (O4 = e) : (np = null, e);
    } catch {
      // Fallback: read account data from webview session/localStorage
      const cached = await getAccountFromWebviewSession();
      if (cached) return (O4 = cached, cached);
      return (np = null, null);
    }
  })(), np);
}

Option B: Use the webview's defaultSession cookies/storage to obtain account info directly, instead of making a separate net.fetch call that's subject to Cloudflare interception.

Option C: For getAccountStorageDir(), cache the last known accountId/orgId to disk so that even if the bootstrap API is unreachable, previously authenticated sessions can still initialize.

Reproduction

This affects users behind corporate firewalls, proxies, or any network where the IP triggers Cloudflare's managed challenge for claude.ai. The key condition is:

  1. User is logged in (webview session active, chat works)
  2. net.fetch from main process to claude.ai/api/bootstrap gets 403 from Cloudflare
  3. Every Cowork session attempt fails immediately

Currently there is no user-accessible workaround.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗