惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
L
LINUX DO - 热门话题
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Hacker News: Ask HN
Hacker News: Ask HN
G
GRAHAM CLULEY
C
CXSECURITY Database RSS Feed - CXSecurity.com
H
Hacker News: Front Page
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Know Your Adversary
Know Your Adversary
A
Arctic Wolf
W
WeLiveSecurity
C
CERT Recently Published Vulnerability Notes
T
Tenable Blog
SecWiki News
SecWiki News
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
T
Threat Research - Cisco Blogs
PCI Perspectives
PCI Perspectives
人人都是产品经理
人人都是产品经理
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 司徒正美
T
Troy Hunt's Blog
The Last Watchdog
The Last Watchdog
S
Securelist
L
LINUX DO - 最新话题
C
Check Point Blog
P
Privacy & Cybersecurity Law Blog
GbyAI
GbyAI
P
Palo Alto Networks Blog
T
Tailwind CSS Blog
小众软件
小众软件
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
C
Cybersecurity and Infrastructure Security Agency CISA
Stack Overflow Blog
Stack Overflow Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Cyberwarzone
Cyberwarzone
Google DeepMind News
Google DeepMind News
AWS News Blog
AWS News Blog
有赞技术团队
有赞技术团队
Y
Y Combinator Blog
MyScale Blog
MyScale Blog
云风的 BLOG
云风的 BLOG
Application and Cybersecurity Blog
Application and Cybersecurity Blog
量子位
博客园 - Franky
博客园_首页
Project Zero
Project Zero
P
Proofpoint News Feed
大猫的无限游戏
大猫的无限游戏
Last Week in AI
Last Week in AI
aimingoo的专栏
aimingoo的专栏

青萍叙事

什么都想做,是一人企业最贵的病 小程序上线30天,注册量超过了网站一整年 注册验证码被刷了一整夜,论接口限流的重要性 通过Hermes搭建AI驱动的全自动安全封禁系统 博客被攻击之后,我部署雷池防火墙给网站穿上防弹衣 个人博客第一次被攻击,评论区被植入恶意代码全记录 青萍AI图床:AI 生图接口文档 真心建议做一人企业的你,先把统一认证搞定 从个人公众号迁移到企业公众号经验总结 豆包千问集体下线智能体,是时候拥抱 Hermes Agent 了 配音兼职渠道怎么找?5 种渠道和 1 个避坑提醒 宝妈声音兼职去哪接单:三个平台横评,最后一个免费入驻每日自动派单 Hermes 浏览器工具:让 AI 替你操作网页 Hindsight for OpenCode:给每个项目装上专属记忆库 Hermes v0.17.0 最值得升级的理由:一个进程,管所有 Profile 音频可视化:从波形到视频的三种实现路径 Hermes 定时任务实战 真人配音兼职怎么找:几个渠道对比,末尾推荐一个免费入驻的 AI语音技术到底能做什么 AI漫剧之配音篇 用 Hermes Agent 搭了一条内容生产线 2026精选5款热门AI配音软件 用 Hermes 打造 LLM Wiki 知识库:Karpathy 方法实战指南 用 Hermes 打造个人知识管理系统 给 Hermes 装上本地记忆:Hindsight 自部署完整记录 给 Hermes Agent 装上慧眼:GLM 视觉 MCP Server 配置指南 AI 配音多音字踩坑记 DeepSeek 5月六连崩,是时候重视 LLM API 故障转移了 做内容为什么最好用真人配音 青萍AI语音开放真人配音师入驻 给 Hermes 装上真记忆:Hindsight 上手指南 Hermes 多 Agent 管理指南 邀请有礼:把好用的 AI 工具分享出去,和朋友一起拿积分 Hermes Agent 基础配置指南:从零搭建你的 AI 管家 推荐一个免费在线音频编辑器,像剪映一样好用 我把小说变成了有声故事,而且每个人物都有自己的声音 FunASR 语音识别:让声音变成可编辑的文字 FFmpeg 音频格式转换实用指南 青萍AI语音多人对话模式:5分钟快速生成播客 青萍创作者平台伙伴共创计划正式启动 免费开源的 Screen Studio 平替!OpenScreen 让你的产品演示瞬间专业起来 语音合成入门:SSML 标记语言快速上手 还在为配音头疼?我宣布:这个免费工具彻底治愈了我 青萍AI语音:用 AI 重新定义你的声音 OpenClaw 记忆对比:向量化 VS Markdown,到底该怎么选? 建站一周年:从 NotionNext 迁移到 Hexo 安知鱼
Hermes Gateway 飞书连接断开排查
青萍叙事 · 2026-06-10 · via 青萍叙事
HermesHermes-Agent运维macOS

前言

昨天手痒,给 Hermes Gateway 换了个新模型。
结果飞书那边突然就收不到消息了。

第一反应是:换模型搞坏了?
赶紧查 Gateway 日志,看到了这么一行:

1
RuntimeError: Executor shutdown has been called

飞书的 WebSocket 连接也断了,Gateway 看起来还在跑,但实际上已经是个空壳。

排查:先看 Gateway 日志

1
2
3
4
5
2026-06-09 14:23:45 INFO  Gateway started
2026-06-09 14:23:46 INFO Feishu WebSocket connected
...
2026-06-09 15:34:12 ERROR RuntimeError: Executor shutdown has been called
2026-06-09 15:34:12 ERROR Feishu WebSocket connection lost

Gateway 14:23 启动,15:34 报错,刚好运行了 1 小时 11 分钟

这个时间点很微妙,不是刚启动就挂,而是运行了一段时间后才出问题。
换模型的操作发生在 15:30 左右,看起来像是换模型触发了问题,但实际上根因不在这里。

根因:Python asyncio executor 的生命周期陷阱

Gateway 是 Python 写的,核心用的是 asyncio event loop。
问题出在 run_in_executor 这个调用上。

Python asyncio 的 loop.run_in_executor() 会把同步代码丢到线程池里执行。
默认情况下,它用的是 event loop 自带的 ThreadPoolExecutor
这个 executor 的生命周期管理有个坑:

如果 executor 的引用计数归零,Python 的垃圾回收器会把它清理掉,调用 shutdown()

在长时间运行的服务里,这种情况可能悄无声息地发生:

1
2
3
4
5
6

async def call_llm(prompt):
loop = asyncio.get_event_loop()

result = await loop.run_in_executor(None, sync_llm_call, prompt)
return result

日志里的 Executor shutdown has been called 就是这么来的。
executor 被关了,所有依赖它的异步任务都会失败,飞书的 WebSocket 消息处理自然也跟着挂了。

加上 macOS 的内存压力管理(memory pressure),长时间运行的 Python 进程更容易触发这类内部状态异常。
系统可能在后台杀掉了一些不活跃的线程,导致 executor 的内部状态被破坏。

发现问题:`–replace` 的坑

最直接的办法就是重启:

1
hermes gateway start

重启后飞书消息恢复正常。
但这只是临时方案,后面还会遇到同样的问题。

这里又踩了一个坑,我发现之前的 Gateway 是用 --replace 模式启动的:

1
hermes gateway start --replace

--replace 的作用是如果已有 Gateway 在跑,先杀掉再启动新的。
听起来很方便对吧?
但在 macOS 上,如果你用了 launchd 来管理服务,--replace 会导致冲突。

Gateway 被 launchd 拉起来后,你手动 --replace 启动一个新的,launchd 不知道旧的被杀了,新旧进程就会打架。

最佳实践:用 launchd 托管 Gateway

macOS 上管理后台服务,launchd 是正道。
先看看现有的配置:

1
ls ~/Library/LaunchAgents/ai.hermes.gateway-*.plist
1
2
/Users/lu/Library/LaunchAgents/ai.hermes.gateway-default.plist
/Users/lu/Library/LaunchAgents/ai.hermes.gateway-work.plist

每个 profile 有一个对应的 plist 文件。
看看里面写了什么:

1
cat ~/Library/LaunchAgents/ai.hermes.gateway-default.plist
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>ai.hermes.gateway-default</string>
<key>ProgramArguments</key>
<array>
<string>/Users/lu/.hermes/bin/hermes</string>
<string>gateway</string>
<string>start</string>
<string>--replace</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
</dict>
</plist>

看到了吧,ProgramArguments 里赫然写着 --replace
这就是问题所在。

批量修复 plist 配置

检查了一下,所有 profile 的 plist 都有这个问题。
手动改太慢,写个脚本批量处理:

1
2

grep -l "\-\-replace" ~/Library/LaunchAgents/ai.hermes.gateway-*.plist
1
2
/Users/lu/Library/LaunchAgents/ai.hermes.gateway-default.plist
/Users/lu/Library/LaunchAgents/ai.hermes.gateway-work.plist

批量移除 --replace

1
2
3
4
5
for plist in ~/Library/LaunchAgents/ai.hermes.gateway-*.plist; do

sed -i '' '/<string>--replace<\/string>/d' "$plist"
echo "Fixed: $plist"
done

重新加载所有 launchd 服务:

1
2
3

launchctl unload ~/Library/LaunchAgents/ai.hermes.gateway-*.plist
launchctl load ~/Library/LaunchAgents/ai.hermes.gateway-*.plist

新版 macOS 上 unload/load 已标记为 legacy,推荐用 bootout/bootstrap

1
2
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/ai.hermes.gateway-*.plist 2>/dev/null
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.hermes.gateway-*.plist

验证一下:

1
launchctl list | grep hermes
1
2
ai.hermes.gateway-default    0    com.apple.xpc.launchd.oneshot.0x10000001.hermes
ai.hermes.gateway-work 0 com.apple.xpc.launchd.oneshot.0x10000002.hermes

状态码是 0,说明正常运行。

踩坑后的教训

asyncio executor 的坑要提前防。

长时间运行的 Python 服务,executor 的生命周期不能完全交给垃圾回收器。
要么显式持有 executor 的引用,要么定期检查 executor 状态。

1
2
3
4
5
6
7
8
9

class Gateway:
def __init__(self):
self._executor = ThreadPoolExecutor(max_workers=4)
self._loop = asyncio.get_event_loop()
self._loop.set_default_executor(self._executor)

async def call_in_background(self, func, *args):
return await self._loop.run_in_executor(self._executor, func, *args)

macOS 服务必须用 launchd 托管。

别用 nohup 后台跑。
别用 --replace
别用 screentmux
launchd 是 macOS 原生的服务管理器,它会帮你处理进程崩溃重启、开机自启、资源限制等问题。

关键配置点:

  • RunAtLoad=true:登录时自动启动
  • KeepAlive.SuccessfulExit=false:正常退出不重启,异常退出才重启
  • 不要加 --replace:让 launchd 管理进程生命周期

遇到连接问题先查日志。

飞书消息发不出去,原因可能很多:网络问题、token 过期、服务挂了……
别上来就重启,先看日志。
日志里的错误信息通常能直接指向问题根因。

1
2
3
4
5

tail -100 ~/.hermes/logs/gateway-default.log


log show --predicate 'processImagePath contains "hermes"' --last 1h

这次排查让我对 Python asyncio 的底层机制有了更深的理解。
asyncio 不是银弹。
executor 管理、事件循环生命周期这些细节,在开发阶段很难暴露,往往要跑到生产环境、运行一段时间后才会出问题。

好在这次只是飞书消息发不出去,不是什么致命故障。
但也提醒我,对于长时间运行的服务,监控和容错设计真的不能偷懒。