macOS 多 Profile QA Cron Runner
场景
专用 QA profile 需要每十分钟扫描一次任务。默认 profile 已运行 Feishu Gateway;QA profile 使用同一个 Feishu app_id,因此第二个 Gateway 被 Hermes 主动拒绝,以避免两个本地 WebSocket 客户端竞争。
决策
- 不停止默认 Gateway;
- QA profile 不承担消息接收;
- 卸载冲突的 QA Gateway service;
- 使用官方
hermes -p qa cron tick作为一次性调度入口; - 用 macOS LaunchAgent 每 60 秒调用 tick;
- QA Job 自身保持
schedule=every 10m、repeat=forever。
关键发现
周期表达式
创建 Cron 时,简写 10m 被解析为“十分钟后执行一次”,并留下 repeat=once。只把 schedule 更新为 every 10m 仍可能保留 once。可靠修复是删除错误 Job,以 every 10m 重新创建,并重新读取确认 repeat=forever。
Gateway 冲突
日志中的有效根因应来自当前启动段,例如:
Another local Hermes gateway is already using this Feishu app_id
此时不应停止未知用途的现有 Gateway。若 QA 不需要消息平台,独立 tick runner 比第二个 Gateway 更安全。
macOS TCC
LaunchAgent 初始 WorkingDirectory 指向 Documents/.../qa,Python 在导入阶段调用 os.getcwd() 即得到:
PermissionError: [Errno 1] Operation not permitted
将调度器和 Cron workdir 改为 profile 目录后,tick 退出码恢复为 0。项目源码目录是否可访问,仍应在真实候选任务中验证;空队列运行不能覆盖该风险。
LaunchAgent 结构
核心参数:
<key>ProgramArguments</key>
<array>
<string>/path/to/hermes/venv/bin/python</string>
<string>-m</string>
<string>hermes_cli.main</string>
<string>--profile</string>
<string>qa</string>
<string>cron</string>
<string>tick</string>
</array>
<key>WorkingDirectory</key>
<string>/path/to/profile/qa</string>
<key>StartInterval</key>
<integer>60</integer>
<key>RunAtLoad</key>
<true/>
stdout/stderr 应写入 profile 日志目录。不要把真实凭证放进 plist。
验证证据链
plutil -lint返回 OK;- launchd service 已 bootstrap;
- 手动 kick 后
runs增长; last exit code = 0;- 清空旧日志后再触发,stdout/stderr 无当前错误;
- Hermes Cron Job 重新读取显示:
every 10m;repeat=forever;- 正确 skills 和 workdir;
- 手动运行 Cron 后
last_status=ok; - 读取持久化输出,而非只看工具返回;
[SILENT]只证明无候选路径,没有证明完整 QA 测试链;- 后续应等待自然周期,并在真实候选出现时验证 Base 认领、源码访问、测试和写回。
状态命令的解释
外部 tick runner 模式下,hermes cron status 可能仍提示 profile Gateway 未运行。不要单独据此判失败或成功;交叉检查:
- launchd
runs与最新退出码; - tick stdout/stderr;
- Cron run history;
- Job last_run/last_status;
- 持久化输出。
一次性 tick 完成后 state = not running 是预期状态,不代表调度器未注册。
macOS TCC Documents 目录后台访问(深坑)
现象
Hermes cron 在交互式终端里能读取 ~/Documents/lemonAI/graphagent/source,但 launchd tick 运行时报:
PermissionError: [Errno 1] Operation not permitted
根因
- LaunchAgent 是后台进程,继承用户 uid 但不继承交互式终端的 TCC 授权;
- macOS 对
Documents/Desktop/Downloads有SystemPolicyDocumentsFolder保护; - 进程身份不是
hermes脚本,而是 plist 中ProgramArguments的第一个可执行文件:即 venv 里的python; - 该
python通常是指向~/.local/share/uv/python/.../python3.11的符号链接。TCC 绑定到真实目标文件,给 symlink 加权限无效。
诊断
# 1. 确认 plist 中真正运行的二进制
ls -la ~/Library/LaunchAgents/ai.hermes.cron-qa.plist
# 看 ProgramArguments 的第一项,例如 /Users/yibo/.hermes/hermes-agent/venv/bin/python
# 2. 解析真实 Python 路径
ls -la /Users/yibo/.hermes/hermes-agent/venv/bin/python
file /Users/yibo/.hermes/hermes-agent/venv/bin/python
# 典型结果:
# python -> /Users/yibo/.local/share/uv/python/cpython-3.11.14-macos-aarch64-none/bin/python3.11
修复(推荐)
- 打开 System Settings → Privacy & Security → Full Disk Access;
- 点击
+,添加 真实 Python 二进制(不是 symlink):/Users/yibo/.local/share/uv/python/cpython-3.11.14-macos-aarch64-none/bin/python3.11如果文件选择器找不到.local/share/uv,按Cmd+Shift+G输入完整路径; - 重启 cron 服务:
bash launchctl kickstart -k gui/$(id - u)/ai.hermes.cron-qa launchctl kickstart -k gui/$(id - u)/ai.hermes.cron-code - 验证日志不再出现
Operation not permitted:bash tail -f /Users/yibo/.hermes/profiles/qa/logs/cron-tick.error.log tail -f /Users/yibo/.hermes/profiles/code/logs/cron-ticker.error.log
替代方案
如果不想给 Python 全盘权限,把项目移出 Documents:
mv /Users/yibo/Documents/lemonAI/graphagent /Users/yibo/graphagent
然后更新对应 Cron Job 的 workdir:
hermes -p <profile> cron update <job-id> --workdir /Users/yibo/graphagent/source
注意:只改 workdir 到 profile 目录但项目仍在 Documents 里无效,Cron 内访问项目时仍会触发 TCC。
检查项
- 不要只给
hermesshell 脚本加权限,进程身份是 Python 二进制; - 不要给
venv/bin/python符号链接加权限,TCC 看的是真实目标文件; - 验证时必须读取真实任务触发的日志,空队列 tick 通过不等于源码访问链路已通;
- 多 profile 场景下,每个 profile 的 LaunchAgent 使用同一个 Python 二进制,只需授权一次。