#1335·cc-haha

[BUG] ��Դ������ electron:dev ʱ�����Ȼ�������ط�������ʧ�� / Failed to fetch������CORS Ԥ�챻 H5 access �Ž��ܾ�

Author: zhouweirunCreated Sep 16, 2026Updated Sep 16, 2026

提交前确认

  • 我已经阅读 README 常见问题
  • 我已经升级到最新版本后复现过这个问题(main @ 0676c194
  • 我已经搜索过现有 issues,确认没有重复问题
  • 我已经隐藏截图和日志中的 API Key、Token、Cookie 等敏感信息

问题描述

按 #783 的建议从源码启动桌面端(bun run --cwd desktop electron:dev)时,界面必然显示启动错误页「本地服务启动失败」+「启动错误 / Failed to fetch」,点「重试」无效。

sidecar 本身完全健康:进程在跑、端口在监听、/health 返回 200。问题出在 renderer 到 sidecar 的跨源请求上。

运行环境

  • 使用方式: 桌面端(源码本地构建)
  • 操作系统: Windows 11 专业版 build 26200 (x64)
  • 桌面端版本: 0.6.3(仓库 main @ 0676c194
  • 安装来源: 源码本地构建
  • Bun 版本: 1.3.14
  • Node 版本: v24.21.0

Provider / 模型配置

与本次问题无关——在未配置任何 provider 的干净环境下同样 100% 复现

复现步骤

  1. git clone https://github.com/NanmiCoder/cc-haha.git && cd cc-haha
  2. bun install
  3. cd desktop && bun install
  4. bun run build:sidecars(生成 desktop/src-tauri/binaries/claude-sidecar-<triple>[.exe]
  5. 回到仓库根目录:bun run --cwd desktop electron:dev
  6. 窗口打开后即显示「本地服务启动失败 / Failed to fetch」

是否稳定复现: (每次必现)

根因

开发模式下 renderer 由 Vite 提供,origin 是 http://localhost:1420;sidecar 监听在 127.0.0.1:<动态端口>。两者跨源,因此 renderer 的每个 API 调用都会先触发 Chromium 的 CORS 预检。

src/server/api/client.ts 给请求带上 Content-Type: application/json,所以预检是必然发生的。而按规范,预检请求不携带 Authorization——协商该头正是预检的目的。

服务端 src/server/h5AccessPolicy.ts 的判定却是:

typescript
function isLocalDesktopOrNavigationOrigin(request, origin, context) {
  if (!origin) return !isCrossSiteSubresource(request.headers)
  if (LOCAL_DESKTOP_ORIGINS.has(origin)) return true   // 只有 'file://'
  if (context.localAccessTokenConfigured) return false // ← dev 渲染进程命中这里
  return isLoopbackBrowserOrigin(origin)
}

桌面端启动 sidecar 时会注入 CC_HAHA_LOCAL_ACCESS_TOKENserverRuntime.tswithLocalAccessToken),于是 localAccessTokenConfigured === true。带 Origin 的预检既不是 file://、又拿不到 token,被判定为 h5-browser,进而命中 shouldBlockDisabledH5Access 返回:

HTTP 403
{"error":"Forbidden","message":"H5 access is disabled. Enable H5 access from the local desktop app first."}

该 403 响应不含 Access-Control-Allow-Origin,浏览器随即拦截真实请求,renderer 只看到 TypeError: Failed to fetch

为什么打包版不受影响:打包后 renderer 从 file:// 加载,而 file://LOCAL_DESKTOP_ORIGINS 白名单里,直接走 local-trusted 分支。所以这是 dev-only 缺陷,正式用户碰不到。

实测证据

对运行中的 dev 应用直接发请求(Origin: http://localhost:1420):

请求 修复前
OPTIONS /api/settings(预检) 403,无 ACAO
GET /api/settings(带 token 的真实请求) 200
GET /api/settingsOrigin: file://,打包版形态) 200
GET /health 200

即:真实请求本身能通过,但预检把它挡在门外

错误信息与日志

界面文案:

本地服务启动失败
启动错误
Failed to fetch

服务端响应体(关键证据):

json
{"error":"Forbidden","message":"H5 access is disabled. Enable H5 access from the local desktop app first."}

期望行为

bun run electron:dev 能正常使用,与打包版一致。

建议修复方向

我已在本地验证过一个最小改动,供参考(未提 PR,等你确认这个问题的定性):

  1. desktop/scripts/electron-dev.ts:dev launcher 通过新变量 CC_HAHA_TRUSTED_RENDERER_ORIGIN已被 resolveRendererEntry 校验过的渲染 URL 下发给 sidecar。
  2. src/server/h5AccessPolicy.ts:新增 resolveTrustedRendererOrigin()(重新校验协议必须是 http(s) 且 host 必须是 loopback),并在 origin 判定中只豁免这一个确切 origin
  3. src/server/index.ts:启动时读取该变量注入请求上下文。

安全性上刻意收紧:

  • 用 dev launcher 专属变量而非 ELECTRON_RENDERER_URL,打包版从构造上不可能继承该豁免;
  • 放开 loopback——实测 Origin: http://localhost:5173http://127.0.0.1:1420http://localhost:1421http://[::1]:1420 仍全部 403;
  • H5 控制面 /api/h5-access/api/h5-access/enable 与破坏性端点 /api/settings/session-cleanup 仍返回 403。

补充说明

  • 本文所述 electron:dev 路径由 #783 的回复推荐("如果想启动完整的 Electron 桌面窗口,可以用 bun run electron:dev"),且 release-notes/v0.5.0.md 记录过对 electron:dev 启动器的修复(#1091),推测此路径仍被维护。
  • CI 中没有任何工作流会启动 dev 渲染进程,因此该问题不会被 CI 拦住。