fix: launchd 下 agent 起不动 → daemon 崩溃循环(实盘挂 pipeline 时发现) - #4
Open
WiseriaAI wants to merge 4 commits into
Open
Conversation
shutil.which() 探测到的是绝对路径,但 detect_agents() 把裸命令名(claude/codex) 写进了 agents.toml。launchd 用最小 PATH(/usr/bin:/bin:/usr/sbin:/sbin), plist 的 ProgramArguments 早就为这个坑改成绝对路径,detect_agents() 漏改。 真实事故:daemon 一起来跑第一个事件就 FileNotFoundError: 'claude'。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LPZPdjxcVcSAKHw5MHLqj9
exec_agent 只 catch TimeoutExpired,agent 二进制找不到时 FileNotFoundError 穿透 poll_once → daemon.run() → 进程死。launchd KeepAlive 立刻重拉,2 分钟内 崩 20 次;而事件的 attempts 从未 bump,连 F8 死信兜底都够不着,永远卡 pending。 FileNotFoundError / PermissionError 都是 OSError 子类,一并兜住返回 127, 让"起不动 agent"退化成一次失败的 run(重试→死信),而不是 daemon 的死。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LPZPdjxcVcSAKHw5MHLqj9
daemon 每几秒崩一次被 launchd KeepAlive 拉起时,每个新进程一启动就写心跳, 心跳永远新鲜 —— 看板存在的全部理由就是别让这种事发生,而它做不到。 core.note_boot() 记录本次启动时刻,并把上一次的挪到 prev_started_at; daemon.run() 每次起来调一次。web.health() 据此加判据:上一个进程活不过 一个轮询周期(它是崩的),且当前进程也还年轻(循环还在继续) → crashloop。 crashloop 判定排在 busy/ok 之前,否则新鲜心跳会被 ok 吃掉。 看板加「启动 <uptime>」行 + 崩溃态下的排障提示(指向 daemon.err.log)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LPZPdjxcVcSAKHw5MHLqj9
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LPZPdjxcVcSAKHw5MHLqj9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
起因:第一次真的把 daemon 挂上 launchd
投了一条真事件,结果:
daemon 崩 → launchd
KeepAlive重拉 → 再崩。2 分钟内崩了 20 次。 事件永远卡在pending、attempts=0,连死信都进不去。而看板全程显示「运行中」。
现有的 113 个测试一个都没抓到 —— 这三个 bug 只在 launchd 的最小 PATH + KeepAlive 环境下才会出现。
三个 bug
1.
detect_agents()写的是相对命令名它用
shutil.which()探测(交互式 shell 的 PATH 里找得到),然后把裸命令名claude写进agents.toml。但 launchd 用最小 PATH(/usr/bin:/bin:/usr/sbin:/sbin),而 claude 在~/.local/bin/—— 找不到。CLAUDE.md里白纸黑字记着这个坑(「ProgramArguments 必须绝对路径 —— launchd 用最小 PATH」),plist 早就修好了,detect_agents()又踩了同一个。修:把
which()的结果(绝对路径)写进模板。2.
exec_agent只 catchTimeoutExpiredFileNotFoundError直接穿透poll_once→daemon.run()→ 进程死 → launchd 重拉 → 再死。无限崩溃循环,而事件的attempts从没被 bump,F8 死信兜底完全够不着。agent 起不动应该是一次失败的 run(重试 → 死信),不该是 daemon 的死。
修:
except OSError→ 返回 127(shell 惯例)。FileNotFoundError/PermissionError都是它的子类。3. 看板在崩溃循环里说「运行中」
心跳区分不了「健康轮询」和「每 2 秒崩一次又被 KeepAlive 拉起来」 —— 每个新进程一启动就写心跳,心跳永远新鲜。
这恰恰是看板存在的理由,而它失败了。
修:加第五态
crashloop(崩溃重启中)。daemon 启动时记started_at,把上一次的挪到prev_started_at。判据:上个进程活不过一个轮询周期(说明它是崩的)且当前进程也还年轻(说明循环还在继续)。当前进程熬过一个轮询周期后告警自动消失——那是一次正常重启,不是循环。判定顺序里
crashloop必须排在ok前面,否则会被新鲜的心跳吃掉。附带
tests/test_cli.py::test_setup_writes_agents_config把 bug 1 的行为当成了正确预期(断言的就是裸命令名)。已改正。实盘验证
修完重挂 launchd,投一条真
day-start:因果链
#8 → #9完整,gate 生效。121 tests passing。
残留风险(本次不修)
agents.toml的路径若含空格(如/Applications/My Tools/claude),exec_agent的shlex.split会把它拆成多个 argv 元素。当前用户路径无空格。真修需要 TOML 转义 + 模板加引号 + 改exec_agent的解析,超出本次范围。🤖 Generated with Claude Code
https://claude.ai/code/session_01LPZPdjxcVcSAKHw5MHLqj9