Windows使用hermes桌面端个人出现的问题

Hermes Agent 桌面端 Windows 崩溃问题排查实录:一个四重连锁故障的故事

前言

最近在使用 Hermes Agent(一个基于 Electron 的 AI Agent 桌面应用)时,遇到了一个棘手的问题:桌面端启动后窗口闪一下就消失,每次启动都需要手动运行修复工具,自动更新后问题还会循环复发。

经过深入排查,发现这不是一个单独的 bug,而是四个问题相互交织形成的死循环。整个排查过程涉及 Electron 底层机制、Windows 进程模型、Python 虚拟环境、以及 node-pty 终端模拟器等多个技术领域,记录下来供大家参考。

环境信息

项目 版本
操作系统 Windows 10 Build 10.0.22621
显卡 NVIDIA GeForce RTX 2060
显卡驱动 32.0.16.1062 (2026-06-11)
Hermes v0.17.0
Electron 40.10.2
Python 3.11.0 (venv)

问题现象

  1. 双击桌面快捷方式启动 Hermes,窗口闪一下就消失
  2. 之前有个修复工具(.bat),每次启动前都要跑一次
  3. Hermes 自动更新后,问题复发,修复工具也要重新跑

第一层:GPU 渲染崩溃

查看 desktop.log,第一条线索很明确:

ini 复制代码
[hermes] [renderer] render-process-gone reason=crashed exitCode=-2147483645

exitCode=-21474836450x80000003,是 Windows 的断点异常(BREAKPOINT EXCEPTION)。这是 Electron 的 GPU 进程崩溃了。

之前的修复工具设置了这些环境变量:

batch 复制代码
set ELECTRON_DISABLE_GPU=1
set ELECTRON_DISABLE_GPU_SANDBOX=1
set ELECTRON_NO_SANDBOX=1

看起来没问题对吧?但当我读了 main.cjs 的源码后发现------这些变量根本没被检查

关键发现:正确的环境变量名

main.cjs 第 74 行,Hermes 检查的是:

javascript 复制代码
const override = String(env2.HERMES_DESKTOP_DISABLE_GPU || "").trim().toLowerCase();
if (GPU_OVERRIDE_ON.has(override)) return "override (HERMES_DESKTOP_DISABLE_GPU)";

正确的变量名是 HERMES_DESKTOP_DISABLE_GPU,不是 ELECTRON_DISABLE_GPU

但环境变量也不够用

即使设置了 HERMES_DESKTOP_DISABLE_GPU=1,它调用的是:

javascript 复制代码
app.disableHardwareAcceleration();
app.commandLine.appendSwitch("disable-gpu-compositing");

实测发现,app.disableHardwareAcceleration() 对 RTX 2060 不够用 ,GPU 进程仍然崩溃。只有 --disable-gpu 这个 Chromium 命令行参数才能彻底阻止 GPU 进程启动。

bash 复制代码
# 测试对比
HERMES_DESKTOP_DISABLE_GPU=1 ./Hermes.exe    # GPU 仍然崩溃 ❌
./Hermes.exe --disable-gpu                    # GPU 不崩溃 ✅

这是 Electron/Chromium 层面的问题,disableHardwareAcceleration() 只是禁用了硬件加速合成,但 GPU 进程本身还是会启动。--disable-gpu 才是彻底禁用 GPU 进程的开关。

第二层:Bootstrap 标记文件缺失

GPU 崩溃只是第一个问题。即使 GPU 修好了,桌面端还是无法正常工作。

查看 main.cjs 的启动逻辑:

javascript 复制代码
function resolveHermesBackend(dashboardArgs) {
    // 第一步:检查 bootstrap 是否完成
    if (isBootstrapComplete()) {
        return createActiveBackend(dashboardArgs);  // ✅ 正常路径
    }
    // 第二步:在 PATH 上找 hermes CLI
    const hermesCommand = findOnPath("hermes");
    if (hermesCommand) {
        // 尝试探测 CLI 是否可用
        if (verifyHermesCli(hermesCommand)) {
            return { /* 正常后端 */ };
        }
        // 探测失败 → 认为需要 bootstrap
    }
    // 第三步:触发 bootstrap 流程
    return { kind: "bootstrap-needed" };
}

isBootstrapComplete() 检查一个标记文件 .hermes-bootstrap-complete

javascript 复制代码
function isBootstrapComplete() {
    const marker = readBootstrapMarker();
    if (!marker || typeof marker !== "object") return false;
    if (marker.schemaVersion !== 1) return false;
    if (typeof marker.pinnedCommit !== "string" || marker.pinnedCommit.length < 7) return false;
    return isHermesSourceRoot(ACTIVE_HERMES_ROOT) && fileExists(getVenvPython(VENV_ROOT));
}

这个标记文件不存在。 所以桌面端每次都走"从未完成初始化"的路径,触发 bootstrap 流程。

第三层:CLI 探测失败

标记文件不存在,代码降级到 PATH 探测。它在 PATH 上找到了 hermes.EXE

css 复制代码
[hermes] Ignoring existing Hermes CLI at C:\...\venv\Scripts\hermes.EXE:
    --version probe failed; falling through to bootstrap.

有意思的是,我在终端里直接运行 hermes.EXE --version 完全正常。但在 Electron 进程内探测失败。

查看探测代码:

javascript 复制代码
function verifyHermesCli(hermesCommand, opts = {}) {
    try {
        execFileSync(hermesCommand, ["--version"], {
            stdio: "ignore",
            timeout: 5000,  // 5秒超时
            shell: false,
            windowsHide: true
        });
        return true;
    } catch {
        return false;  // 超时或异常都返回 false
    }
}

探测失败的可能原因:

  • Electron 进程的 DLL 搜索路径与终端不同
  • Python shim(hermes.exe)加载 Python DLL 时环境差异
  • 5秒超时在某些情况下不够

第四层:更新死循环

当 CLI 探测也失败后,代码触发 handOffWindowsBootstrapRecovery()

javascript 复制代码
async function handOffWindowsBootstrapRecovery(reason) {
    const updater = resolveUpdaterBinary();
    const child = spawn(updater, ["--update", "--branch", "main"], {
        detached: true,
        stdio: "ignore",
        windowsHide: false  // 注意:这里是 false!
    });
    child.unref();
    setTimeout(() => app.quit(), UPDATE_HANDOFF_DWELL_MS);
    return true;
}

这个函数做了两件事:

  1. 启动 hermes-setup.exe --update(一个终端程序,windowsHide: false,所以会弹黑窗口)
  2. 退出桌面端

hermes-setup.exe 更新完成后会重启桌面端,但不带 --disable-gpu 参数,于是 GPU 又崩了,又触发探测失败,又触发 bootstrap......死循环形成。

arduino 复制代码
GPU 崩溃 → CLI 探测失败 → 触发 bootstrap
    → hermes-setup.exe 弹出(闪一下的黑窗口)
    → 更新完成 → 重启桌面端(不带 --disable-gpu)
    → GPU 又崩 → 循环 ♻️

额外发现:node-pty 的 CMD 窗口闪烁

解决了上述四个问题后,桌面端能正常启动了。但又发现:每次 agent 执行终端命令时,都会有一个 CMD 黑窗口闪烁一下

排查发现是 node-pty(Electron 内嵌的终端模拟器)在 Windows 上的行为:

javascript 复制代码
const ptyProcess = nodePty.spawn(command, args, {
    cols, cwd, env: terminalShellEnv(),
    name: "xterm-256color", rows
});

node-pty 在 Windows 上使用 conpty(Windows Console API)创建伪终端,每次都会创建一个 conhost.exe 进程,表现为短暂的 CMD 窗口闪烁。

这是 node-pty 的已知限制------windowsHide: true 对它无效,因为它不走 Node.js 的 child_process.spawn,而是直接调用 Windows Console API。

修复方案

1. 创建 Bootstrap 标记文件

json 复制代码
{
  "schemaVersion": 1,
  "pinnedCommit": "7cd5eaa646f1...",
  "pinnedBranch": "main",
  "completedAt": "2026-06-26T08:50:00.000Z",
  "desktopVersion": "0.17.0"
}

放到 C:\Users\cj\AppData\Local\hermes\hermes-agent\.hermes-bootstrap-complete

2. 用 VBScript 启动隐藏窗口

vbscript 复制代码
Set WshShell = CreateObject("WScript.Shell")
WshShell.Run """...\Hermes.exe"" --disable-gpu --disable-software-rasterizer --no-sandbox --no-update-check", 0, False

WshShell.Run 的第二个参数 0 表示完全隐藏窗口。

3. 设置正确的环境变量(保底)

powershell 复制代码
# 唯一被代码检查的变量
[Environment]::SetEnvironmentVariable('HERMES_DESKTOP_DISABLE_GPU', '1', 'User')

4. 清理无效的环境变量

之前的修复工具设置了 9 个代码根本不检查的变量,全部删除。

排查过程中的收获

1. 不要盲信环境变量名

ELECTRON_DISABLE_GPU 看起来很官方,但 Hermes 检查的是 HERMES_DESKTOP_DISABLE_GPU读源码比猜变量名靠谱。

2. app.disableHardwareAcceleration()--disable-gpu 不是一回事

前者只禁用硬件加速合成,GPU 进程仍然会启动;后者才彻底禁用 GPU 进程。对某些显卡来说,只有后者才有效。

3. 日志时间戳很重要

desktop.log 最后写入时间是 6 月 21 日,但问题发生在 6 月 26 日。说明新版本的崩溃比旧版本更严重------连日志都来不及写就崩了。

4. 检查标记文件和状态文件

很多应用用标记文件记录初始化状态。当出现"重复初始化"的问题时,先检查标记文件是否存在。

5. node-pty 在 Windows 上会弹窗

如果 Electron 应用内嵌了终端功能,node-pty 在 Windows 上会短暂显示 CMD 窗口。这是底层限制,需要用 CREATE_NO_WINDOW 标志或替代方案解决。

已提交 Issue

这个问题已向 Hermes 上游提交: github.com/NousResearc...


排查这类问题的核心思路:不要假设,要验证。 每一个"看起来对"的修复,都要实际测试确认它真的生效了。尤其是环境变量、启动参数这类配置,写错了不会报错,只会默默无效。

相关推荐
leeyi2 小时前
Agent Transfer:让 AI 把任务交给更合适的 AI
aigc·agent·ai编程
后端小肥肠2 小时前
Codex + Obsidian 做人生副本视频:输入主题文案,直通剪映草稿
人工智能·aigc·agent
花椒技术3 小时前
Agent 不只会聊天:我们如何用 CLI 整理业务能力入口
agent·ai编程·mcp
DigitalOcean4 小时前
在云端运行 Codex —— DigitalOcean Codex 插件正式推出
agent
FanetheDivine5 小时前
学习Agent开发6 langgraph速览
agent·ai编程
前端君7 小时前
Claude Code 如何配置本地Ollama模型或别的模型(Deepseek等)
llm·agent·claude
程序员小假7 小时前
RAG文档存储与切割策略详解:从基础到进阶
agent