Carry の Blog Carry の Blog
首页
  • Nginx
  • Prometheus
  • Iptables
  • Systemd
  • Firewalld
  • Docker
  • Sshd
  • DBA工作笔记
  • MySQL
  • Redis
  • TiDB
  • Elasticsearch
  • OpenClaw
  • Hermes Agent
  • Claude Code
  • MySQL8-SOP手册
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Carry の Blog

好记性不如烂键盘
首页
  • Nginx
  • Prometheus
  • Iptables
  • Systemd
  • Firewalld
  • Docker
  • Sshd
  • DBA工作笔记
  • MySQL
  • Redis
  • TiDB
  • Elasticsearch
  • OpenClaw
  • Hermes Agent
  • Claude Code
  • MySQL8-SOP手册
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • OpenClaw

  • Hermes-Agent

    • Hermes Agent 概述
    • Hermes Agent 实战 01|架构总览:用一个 Agent 管一整个机房
    • Hermes Agent 实战 02|多 Profile 与超管模型:一个 Agent 安全地管十几台机器
    • Hermes Agent 实战 03|Gateway 运维:systemd、裸进程,和一个 Telegram token 撞车
    • Hermes Agent 实战 04|模型路由实战:config 全解、thinking 注入与 401/503 源码级根因
    • Hermes Agent 实战 05|技能工程:写、去重、pin,与每周自我审计
    • Hermes Agent 实战 06|让 Agent 自己上班:cron 驱动的无人值守巡检
    • Hermes Agent 实战 07|数据库实战:可直接抄走的 SQL Server 巡检脚本
    • Hermes Agent 实战 08|量化交易助手:持仓盈亏、网格减仓,与「没开单」的真相
    • Hermes Agent 实战 09|接入 OpenWebUI:把每个 Profile 暴露成一个「模型」
    • Hermes Agent 实战 10|升级不翻车,与给上游提 PR:一个被冲掉三次的修复
      • 1. 升级的标准动作
      • 2. 本地补丁会被上游「静默冲掉」
      • 3. 每次升级后,验证补丁还活着
      • 4. 已知会失败、但不是回归的测试
      • 5. 把修复反哺给上游
      • 6. 可复现 checklist
      • 6. 升级后的完整验证流程
        • 6.1 当 git merge 真的冲突了
        • 6.2 测试失败——区分"真回归"还是"本机环境"
      • 7. Agent 可直接解析的元数据块
    • Hermes Agent 实战 11|踩坑合集:当「手动 rm」从来不是真正的修复
    • Hermes Agent 实战 12|工具链外延:用 AI 运维 AI,与这个系列的诞生
    • Hermes Agent 实战 13|旗舰篇:让 Agent 从零部署并灾难恢复一个 7 节点生产集群
  • Claude-Code

  • AI-Agent
  • Hermes-Agent
Carry の Blog
2026-06-22
目录

Hermes Agent 实战 10|升级不翻车,与给上游提 PR:一个被冲掉三次的修复原创

# 升级不翻车,与给上游提 PR:一个被冲掉三次的修复

系列第 10 篇。Hermes 是活跃开源项目,频繁更新。但我的 main 上有本地独有的补丁——最关键的一个是跨 Profile SSH 泄漏修复。这篇讲怎么合并上游又不丢本地补丁、怎么验证补丁还活着、以及那个被上游合并冲掉了三次的修复的完整复盘,最后讲怎么把它反哺成 PR。

# 1. 升级的标准动作

cd ~/.hermes/hermes-agent
git merge origin/main            # 合并上游
uv sync --all-extras             # 同步依赖(注意:必须 --all-extras)
scripts/run_tests.sh             # 跑测试
1
2
3
4

两个本地特有的坑先说:

  • 依赖必须 uv sync --all-extras,不能是裸 uv sync。裸 uv sync 会把环境裁剪到「锁文件 + 默认 extra」,删掉所有可选 extra(各类 IM 平台 SDK、数据库驱动……),让一堆 Profile 静默失效。
  • 改完任何源码/依赖都要重启网关——Python 在 import 时缓存模块,不重启等于没改。

# 2. 本地补丁会被上游「静默冲掉」

main 领先 origin/main 若干本地提交。合并上游时,上游对同一片代码的重写会悄无声息地盖掉你的修复——不报冲突,因为它整段重写了那个函数。

我那个跨 Profile SSH 泄漏修复(核心是让 _resolve_container_task_id() 按会话返回 session:<key> 而不是 "default"),就这样被冲掉了三次:

时间 事件
原始修复 把容器 task_id 按会话隔离
第一次合并 上游重写了这个函数 → 修复静默丢失
重新应用 以兼容上游新设计的方式重打
第二次合并 本地 main 被快进到一个上游 commit,本地提交整个不在祖先链里了
再次重应用 打在上游新的函数体上,并补回被上游删掉的回归测试

教训:「合并没冲突」不代表「你的补丁还在」。上游重写 + 快进,都能让你的修复人间蒸发,而 Git 不会警告你。

# 3. 每次升级后,验证补丁还活着

光记得「我打过补丁」没用,得有可执行的验证步骤。我给这个修复配了固定的 4 步自检:

# 1. 修复点还在该在的函数里
grep -n "HERMES_SESSION_KEY" tools/terminal_tool.py
# 必须出现在 _resolve_container_task_id 内(别和 sudo 密码缓存那个同名修复搞混)

# 2. 该函数没有退化成无条件 return "default"
# 3. 回归测试必须过
scripts/run_tests.sh tests/tools/test_shared_container_task_id.py

# 4. 重启 webui + 网关(Python 模块缓存)
1
2
3
4
5
6
7
8
9

可复现的通法:任何「本地领先于上游」的关键补丁,都要做三件事——

  1. 把它保留成一个带 tag 的 commit(丢了能从 tag 找回);
  2. 写一段能跑的验证(grep + 测试),而不是靠记忆;
  3. 每次合并后立刻跑这段验证,把「补丁还在吗」变成一条命令而不是一次祈祷。

# 4. 已知会失败、但不是回归的测试

升级后跑测试,要能区分「真回归」和「本机环境本来就过不了的」。我维护了一张已知良性失败清单(缺某个可选依赖、home 路径特殊、host ripgrep 行为差异等),写在项目的 CLAUDE.md 里。没有这张清单,你每次升级都会被一堆红色吓到,然后逐渐麻木到漏掉真回归。这张清单本身就是升级流程的一部分。

# 5. 把修复反哺给上游

本地补丁终究是负担——每次升级都要盯。正解是让上游合并它,负担就归零了。我把 SSH 泄漏修复提了 PR,也踩了开源协作的现实:

有价值的 PR 提完,要在评论里 @ 一下核心维护者。

主合并人几乎只合自己和核心团队的 PR。社区 PR——哪怕被打了高优先级标签——不主动 ping 就石沉大海。我那个 P1 的 SSH 修复挂着标签躺了好几天没人看,直到我去评论区 @ 维护者解释这个 bug 才有动静。triage 机器人贴标签很快,但贴标签 ≠ 有维护者会看到。

还有一条我自己的规矩:提交给开源项目的 commit,不要带 AI 的 Co-Authored-By 签名——有些维护者介意 AI 协作者,会因此拒掉 PR。

# 6. 可复现 checklist

  1. 升级三件套:git merge origin/main → uv sync --all-extras → 跑测试 → 重启网关。
  2. 本地关键补丁:保留成带 tag 的 commit + 写可执行验证 + 每次合并后立刻验。
  3. 「无冲突」别松懈——上游重写/快进会静默吞掉补丁。
  4. 维护一张已知良性失败清单,把真回归从噪声里捞出来。
  5. 能上游就上游:提 PR 消灭本地负担;提完 @ 维护者;commit 别带 AI 署名。

下一篇是踩坑合集——那些不够单独成篇、但每一个都真实坑过我的小事:checkpoint 把磁盘吃满、Profile 目录莫名其妙多出空文件夹、以及为什么「手动 rm」从来不是真正的修复。


# 6. 升级后的完整验证流程

把"补丁还在吗"变成一条命令而不是一次祈祷——这是我每次合并上游都跑的 6 步:

# 1. 确认本地干净、当前在 main
cd ~/.hermes/hermes-agent
git status --p<PASSWORD> | wc -l   # 必须 0
git branch --show-current        # 必须 main

# 2. 拉上游 + 合并
git fetch origin
git merge origin/main
git log --oneline -5

# 3. 同步依赖(必须 --all-extras)
uv sync --all-extras

# 4. 跑测试(区分真回归 vs 已知失败)
scripts/run_tests.sh 2>&1 | tee /tmp/test.log

# 5. 验证本地补丁还活着(以 SSH 泄漏修复为例)
grep -n "HERMES_SESSION_KEY" tools/terminal_tool.py
scripts/run_tests.sh tests/tools/test_shared_container_task_id.py

# 6. 重启服务(Python 模块缓存)
systemctl --user restart 'hermes-gateway-*'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 6.1 当 git merge 真的冲突了

git status
git mergetool
git add <file>
git merge --continue
# 再次跑第 5 步验证本地补丁还在
1
2
3
4
5

# 6.2 测试失败——区分"真回归"还是"本机环境"

失败模式 是回归吗 怎么办
tests/acp/* 全部 ❌ 已知(缺 acp 可选依赖) 跳过
test_wecom_callback ❌ 已知(缺 defusedxml) 跳过
test_resolve_path / test_file_sync_back ❌ 已知(home 路径特殊) 跳过
test_search_error_guard.py::test_files_only_excludes_diagnostics[_search_with_rg] ❌ 已知(host ripgrep 行为差异) 跳过
其他任何失败 ✅ 极可能是回归 回滚 git merge,先解决再继续

# 7. Agent 可直接解析的元数据块

{
  "_meta": {
    "doc_version": "2025-01-15",
    "article_id": "hermes-10-upgrade-pr",
    "profile_context": "dev",
    "executable_examples": true,
    "dependencies": ["git", "uv", "pytest"],
    "estimated_setup_time": "60min"
  },
  "quick_start": {
    "prepare_branch": "git checkout main && git pull origin main",
    "merge_upstream": "git fetch origin && git merge origin/main",
    "sync_deps": "uv sync --all-extras",
    "run_tests": "scripts/run_tests.sh 2>&1 | tee /tmp/test.log",
    "restart_services": "systemctl --user restart 'hermes-gateway-*'"
  },
  "test_matrix": {
    "full_suite": "scripts/run_tests.sh",
    "single_file": "scripts/run_tests.sh tests/path/to/test.py",
    "with_args": "scripts/run_tests.sh tests/foo.py -- --tb=long -v",
    "excluded": ["tests/acp/*", "test_wecom_callback", "test_resolve_path"]
  },
  "known_failures_ignore": [
    "tests/acp/* (optional acp dep absent)",
    "test_wecom_callback (defusedxml absent)",
    "test_resolve_path (home resolves to /data/<USERNAME>/home, no SSH backend)",
    "test_file_sync_back (home resolves to /data/<USERNAME>/home, no SSH backend)",
    "test_search_error_guard.py::test_files_only_excludes_diagnostics[_search_with_rg]"
  ],
  "safety_rules": [
    "升级前必须在 main 分支且干净 (git status --p<PASSWORD> | wc -l == 0)",
    "uv sync --all-extras 后必须跑 tests",
    "改源码必须重启 gateway 才生效",
    "永远不要 force push 到 main",
    "本地关键补丁升级后必须 grep 验证还在"
  ],
  "verification": {
    "git_clean": "git status --p<PASSWORD> | wc -l | xargs test 0 -eq",
    "on_main": "git branch --show-current | grep -q '^main$'",
    "uv_lock_fresh": "test uv.lock -nt pyproject.toml",
    "tests_pass": "grep -E 'passed|failed' /tmp/test.log | tail -5",
    "gateways_up": "systemctl --user is-active hermes-gateway-default.service",
    "local_patch_alive": "grep -q 'HERMES_SESSION_KEY' tools/terminal_tool.py"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

AI Agent 使用指南:

  • 当用户说"我要升级 hermes" → 按顺序执行 quick_start.prepare_branch → quick_start.merge_upstream → quick_start.sync_deps → quick_start.run_tests
  • 当用户说"检查升级前状态" → 依次执行 verification.git_clean, verification.on_main, verification.uv_lock_fresh
  • 当用户说"测试失败怎么办" → 对照 known_failures_ignore 列表,如果是已知失败则跳过,否则检查依赖或回滚
  • 当用户说"我的本地补丁没了" → 执行 verification.local_patch_alive 验证,若失败用 git log --all --oneline | grep <keyword> 找回带 tag 的 commit
  • 当用户说"重启服务" → 执行 quick_start.restart_services
#AI Agent#Hermes#升级#开源#Git
上次更新: 6/21/2026

← Hermes Agent 实战 09|接入 OpenWebUI:把每个 Profile 暴露成一个「模型」 Hermes Agent 实战 11|踩坑合集:当「手动 rm」从来不是真正的修复→

最近更新
01
Hermes Agent 实战 13|旗舰篇:让 Agent 从零部署并灾难恢复一个 7 节点生产集群 原创
06-22
02
Hermes Agent 实战 12|工具链外延:用 AI 运维 AI,与这个系列的诞生 原创
06-22
03
Hermes Agent 实战 11|踩坑合集:当「手动 rm」从来不是真正的修复 原创
06-22
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式