【免费下载链接】hermes-workspace
Native web workspace for Hermes Agent — chat, terminal, memory, skills, inspector.
本文以 Hermes Workspace 仓库中的迭代交接文档
memory/goals/2026-05-03-playground-training-grounds/iterations/008-3002-loading-loop-handoff.md为骨架,系统梳理一次典型的"后端全部健康、前端却持续转圈"的加载循环排查过程。文章将带你还原完整的排障链路:环境变量与双端口对比、健康探针验证、Shell 兜底补丁的实现原理,以及针对 UI 状态机、路由漂移、生成路由树与浏览器残留状态的五大候选假设与验证步骤,适合所有遇到"服务明明正常但页面死循环 loading"问题的前端开发者直接复用。
一、问题背景:一次发生在双开发服务器之间的加载循环
本次排障发生在 HermesWorld 功能合并进本地main分支并推送到 GitHub 之后。交接文档记录了如下关键现场(仓库路径:交接文档):
- 功能分支:
feat/agent-view-port-from-controlsuite; - 合并方式:先将本地
main合入功能分支解决冲突、构建通过,再把功能分支合回本地main,最后推送main到远端(推送区间8f31e113b..cb2ecec5f); - 产品状态:HermesWorld 已合入
main并上线,3005的 preview/worktree 行为正常,但3002(本地 main 工作区 dev server)出现了持续加载循环,即使反复合并、刷新也无法恢复。
值得注意的合并冲突解决策略被明确记录:dashboard / agent-view / chat 布局一律以main为准,而 HermesWorld 品牌与多人环境配置在冲突处予以保留。本次冲突涉及四个文件:
.env.examplesrc/components/agent-view/agent-view-panel.tsxsrc/screens/chat/chat-screen.tsxsrc/screens/dashboard/dashboard-screen.tsx
这一策略本身即为排障提供了第一条线索:如果加载循环来自布局层面,那么"以 main 为准"的冲突裁决可能把某个只在功能分支上正确的状态带歪了。
二、已完成的排查动作:把传输与认证层先全部排除
交接文档详细记录了在将问题移交前已经做过的六类验证动作,这些动作构成了一个标准的"自底向上"排查模板。
1. 发现双端口后端不一致(首个真实线索)
3002与3005两个 dev server 起初分别对接了不同的后端:
3002→ portable backend,端口8645;3005→ enhanced backend,端口8642。
这种错配在对比 HermesWorld 行为时具有误导性:两个前端看到的网关能力完全不同,任何功能差异都可能被错误归因。文档明确将其判定为"likely wrong for comparing HermesWorld behavior"。
2. 修改本地 main 的.env
通过编辑/Users/aurora/hermes-workspace/.env,把两处后端地址从8645统一切到8642:
HERMES_API_URL=http://127.0.0.1:8645 → http://127.0.0.1:8642 CLAUDE_API_URL=http://127.0.0.1:8645 → http://127.0.0.1:8642从仓库源码看,这两个环境变量在前端请求链中扮演核心角色:src/server/gateway-capabilities.ts以及src/routes/api/*下大量路由(如gateway-status.ts、auth-check.ts、connection-status.ts、models.ts、claude-proxy/$.ts)都从该模块解析CLAUDE_API、BEARER_TOKEN等网关地址,前端会话、模型列表、技能、任务等能力全部依赖这一基础配置。vite.config.ts中同样存在对HERMES_API_URL的处理,前端侧请求经 Vite 代理转发到该地址。
3. 反复重启与 Vite 缓存清理
3002在以下操作后均被多次重启:
- 切换后端 URL;
- 删除
node_modules/.vite(Vite 预构建缓存目录)以清除失效的依赖预构建产物; - 干净的
pnpm dev重新启动。
4. 验证健康探针端点
重启后3002报告的观测结果全部健康:
GET /api/connection-status → {"ok":true,"mode":"enhanced","backend":"http://127.0.0.1:8642"} GET /api/auth-check → {"authenticated":true,"authRequired":false} GET /api/gateway-status → 合法 JSON(3002 与 3005 均正常)对照源码可以确认这些端点的实际语义。以 connection-status.ts 为例,它聚合了网关探测结果(ensureGatewayProbed())、config.yaml中的活动模型,并计算出status、chatReady、modelConfigured、chatMode、capabilities等字段;chatMode取值enhanced-claude/portable/disconnected。而auth-check.ts路由则直接基于ensureGatewayProbed的探测结果判定是否需要认证。也就是说,"auth 端点健康"意味着网关探测链路本身是通的。
5. 一条值得警惕的假线索
排查中曾发现/api/gateway-capabilities返回了应用 HTML 而非 JSON,一度非常可疑。但随后确认当前main上真实路由是/api/gateway-status,且该端点健康,因此判定为假线索。这提示了一个排障要点:先确认你要打的端点在当前分支上是否真的存在,文件系统路由(TanStack File Router)中过期的历史路由名容易制造噪音。
6. 加入 Shell 兜底补丁
为应对"启动遮罩卡死"场景,在src/components/workspace-shell.tsx中加入了兜底检查。该补丁目前仍在仓库中,可从源码直接验证其实现(workspace-shell.tsx):
// 导入 fetchClaudeAuthStatus useEffect(() => { if (typeof window === 'undefined' || connectionVerified) return let cancelled = false const verify = async () => { try { const status = await fetchClaudeAuthStatus(3000) // ① /api/auth-check,3s 超时 if (cancelled) return setAuthStatus(status) setConnectionVerified(true) return } catch { // ② 失败则降级到 /api/connection-status } try { const res = await fetch('/api/connection-status', { cache: 'no-store' }) if (!res.ok || cancelled) return const data = await res.json() if (data?.ok || (data?.chatReady && data?.modelConfigured)) { setAuthStatus({ authenticated: true, authRequired: false }) setConnectionVerified(true) } } catch { // 两个探针都失败时保持启动屏 } } void verify() return () => { cancelled = true } }, [connectionVerified])补丁的意图非常明确(代码注释原文):即使ConnectionStartupScreen自身卡住,只要/api/auth-check或/api/connection-status健康,Shell 仍应解锁。注意补丁后3002也被重启过,但循环依旧。
兜底补丁背后:启动状态机的真实结构
结合workspace-shell.tsx与connection-startup-screen.tsx的源码,可以完整还原启动状态机的控制流:
- Shell 维护
authStatus与connectionVerified两个状态,并派生出authState = { checked: !isClient || connectionVerified, authenticated, authRequired }(workspace-shell.tsx); - 渲染时,只要
!authState.checked就挂载全屏ConnectionStartupScreen(workspace-shell.tsx),否则正常渲染子路由;若authRequired && !authenticated则先渲染LoginScreen(workspace-shell.tsx); ConnectionStartupScreen内部有一个2 秒轮询 + 5 秒失败面板 + 4 秒静默自动启动的状态机(connection-startup-screen.tsx):
const POLL_INTERVAL_MS = 2_000 // 失败后每 2s 重试一次 tryConnect const FAILURE_REVEAL_MS = 5_000 // 5s 未连上则展示失败/设置面板 const AUTO_START_DELAY_MS = 4_000 // 4s 后静默 POST /api/start-claude 尝试拉起网关其连接循环逻辑为:调用fetchClaudeAuthStatus()(内部请求/api/auth-check,默认 5 秒超时,见 claude-auth.ts);成功则回调onConnected(status)令 Shell 解锁;失败则setTimeout(tryConnect, POLL_INTERVAL_MS)继续轮询。只要/api/auth-check反复返回异常(而非快速成功),这个 2 秒轮询在外观上就是一个永不停歇的 loading loop——这正与文档"健康端点 + 循环仍在"的现象高度吻合。
- 启动屏还提供了"Auto-Start Hermes Agent Gateway"按钮(
POST /api/start-claude)、服务器日志展示,以及按平台(macOS/Windows/Linux)区分的四步手动配置指南(HERMES_API_URL指向任意 OpenAI 兼容后端 → 安装 hermes-agent →hermes setup→hermes gateway run,其中网关即监听:8642)。
三、遗留谜团与五条强假设
完成上述排查后,结论是:"后端、认证、网关全部健康 + Shell 兜底补丁已生效"却仍然循环。据此文档给出了五种候选解释,并给每条假设配套了可执行的验证动作——这部分是本文最具复用价值的排障清单。
假设 A:根本不是启动/认证遮罩
ConnectionStartupScreen可能压根没有出现在屏幕上,用户看到的可能是 splash、路由 shell、playground 过渡加载页或其他 loader。验证动作:立刻向使用者要一张截图,精确识别屏幕上的组件;必要时在疑似组件中临时加入极其醒目的调试文本以区分是哪一个。
假设 B:main 与 worktree 的路由/布局漂移
对照3002(main)与3005(可用的 worktree preview)之间的文件差异:
src/components/workspace-shell.tsxsrc/components/connection-startup-screen.tsxsrc/routes/__root.tsxsrc/routes/playground.tsxsrc/screens/playground/playground-screen.tsxsrc/screens/chat/components/chat-sidebar.tsx
这正是"合并冲突以 main 为准"策略下最可能出现隐性漂移的区域——布局 shell 的某一处小差异(如isChromeFreeSurface、isOnPlaygroundRoute等条件分支)就可能让 3002 停在某个中间态。
假设 C:生成的路由树 / Vite dev 状态异常
开发日志反复出现两类告警:
send-stream-live-tools.ts does not export a Route routeTree.gen.ts was modified by another process during processing前者无害,后者则暗示routeTree.gen.ts(TanStack Router 自动生成的路由注册文件)在多个进程间互相改写,可能让3002内存中持有损坏的路由状态。验证动作:检查3002是否在服务一份错误的内存态,必要时删除生成的routeTree.gen.ts与node_modules/.vite后冷启动。
假设 D:浏览器侧残留状态
包括localStorage/sessionStorage、持久化的 Zustand 状态、Service Worker 缓存、过期路由状态等。验证动作:检查 onboarding 完成标志、持久化的 auth/loading 标志,并尝试无痕窗口 + 清缓存复现。
假设 E:HermesWorld 过渡加载器自身在循环
playground-screen.tsx中存在transitioning状态与TransitionLoadingScreen组件(源码可见,playground-screen.tsx):它是一个z-[95]的全屏过渡层,用金色"entering — 世界名"标题、无限循环的hermes-loading-bar动画与随机 Hermes 语录构成,active为真时透明度为 1。验证动作:搜索transitioning、TransitionLoadingScreen与 playground 路由进入逻辑,检查active是否因某个状态未复位而永远为true——若如此,这便是一个完全独立于后端健康状态的纯 UI 级循环。
文档给出的总体结论也印证了这一点:问题已不再是简单的后端/认证故障,更可能是一个具体的 UI 组件/状态机,或 3002 相对 3005 存在的路由/布局/状态分歧。
四、可复用的排障命令与观测基线
交接文档沉淀了一组可直接复用的探针命令,适用于任何端口:
# 3002 健康三连 curl -s http://localhost:3002/api/connection-status curl -s http://localhost:3002/api/auth-check curl -s http://localhost:3002/api/gateway-status # 3005 对比 curl -s http://localhost:3005/api/connection-status curl -s http://localhost:3005/api/gateway-status排障接近尾声时3002达到的健康基线:
| 观测项 | 期望值 |
|---|---|
mode | enhanced |
backend | http://127.0.0.1:8642 |
auth-check | authenticated: true |
gateway-status | 合法 JSON |
开发日志位于/tmp/hermes3002.log,可配合tail -f实时观察;需要重点过滤的两类噪音见上文假设 C。
五、给后续接手者的六步行动清单
- 先要截图:确定循环属于启动/auth 遮罩、splash、路由 shell、playground 过渡加载页还是其他 loader;
- 对比 3002 与 3005 的路由/布局状态:重点看
workspace-shell.tsx、__root.tsx与启动/auth 流程; - 确认兜底补丁真的编译进了 3002:检查构建产物或运行时行为;
- 审计客户端状态假设:
localStorage/sessionStorage键、onboarding 完成标志、持久化的 auth/loading 标志; - 检查 HermesWorld 过渡加载器自身是否在循环:搜索
transitioning与TransitionLoadingScreen及 playground 路由进入逻辑; - 必要时为疑似遮罩组件临时加上超醒目的调试文本,让使用者直接报出屏幕上到底是哪一个组件。
六、经验沉淀:这类问题的通用诊断顺序
回顾整场排障,可以提炼出适用于任何"服务健康但前端死循环"场景的诊断顺序:
- 先证伪传输层:统一前后端环境变量(
HERMES_API_URL/CLAUDE_API_URL),确保对比实验的两个 dev server 指向同一个后端; - 再证伪探针层:用 curl 逐个验证健康端点,并确认端点在该分支上真实存在(警惕过期路由名制造的假线索);
- 再证伪遮罩层:为启动组件加入 shell 级兜底验证(如本仓库
workspace-shell.tsx中的降级探测),同时核对启动屏内部的轮询/超时常量是否与探针行为匹配; - 最后攻坚 UI 层:聚焦状态机(
transitioning一类布尔状态是否永久卡真)、路由生成产物(routeTree.gen.ts多进程改写)、以及浏览器持久化状态。
当"所有探针都绿、页面仍在转圈"时,问题的边界几乎必然落在 UI 状态机或开发态的路由/布局分歧上——这正是本次交接文档最终给出的结论,也是排查同类问题时应最先怀疑的领域。仓库中的完整交接记录(008-3002-loading-loop-handoff.md)可作为后续继续排查的起点;相关的启动组件、认证工具与网关能力源码(connection-startup-screen.tsx、claude-auth.ts、gateway-capabilities.ts)可随时按需深入。
【免费下载链接】hermes-workspace
Native web workspace for Hermes Agent — chat, terminal, memory, skills, inspector.
相关推荐
Hermes Workspace 开发服务器加载死循环排障实录:多实例 Vite 争抢 TanStack Router 生成文件根因分析
Hermes Workspace 开发服务器加载死循环排障实录:多实例 Vite 争抢 TanStack Router 生成文件根因分析 导读 这是一份基于 H
猫抓(cat-catch)资源嗅探实战指南:3种安装方式拿取页面视频与m3u8流媒体
猫抓(cat catch)资源嗅探实战指南:3种安装方式拿取页面视频与m3u8流媒体 猫抓(cat catch)是一款免费开源的 浏览器媒体资源嗅探扩展 ,它能
音视频ingress-nginx健康检查:后端服务状态监控
ingress nginx健康检查:后端服务状态监控 在现代微服务架构中,确保服务的高可用性是至关重要的。ingress nginx作为Kubernetes集群
后端API网关负载均衡云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考