[design] bg-session 足迹不死是同一个根:统一用 claude agents --json 作 liveness 真相源
提给 forge-launcher;跨引 forge-hub #36、本仓 #8 /#11 。
不是新 bug 报告,是把已存在的几条 issue 归并到同一个根 ,并提一个能同时止血两边的杠杆。
(命令名、版本事实、ghost 过滤实现状态均已对 claude v2.1.154 + forge-hub HEAD 代码核过。)
TL;DR
#8(launcher 看 bg session "仍在运行")、#11(launcher 清不掉僵死 daemon job)、forge-hub #36(/instances 无限累积 ghost)——表面三个独立症状,根是同一个:Claude Code 的 bg-fleet daemon 通过 auto-adopt + claim-spare 把一个 bg session 的"足迹"留住/复活,而没有任何一方对这个足迹做原子拆除 。Hub 和 launcher 各自维护一份"谁还活着"的账本(Hub 的 instance-identities.json / launcher 的 kill(pid,0) 存活判定),两份账本都会被 daemon 的复活机制弄脏。
提议:liveness 的真相源统一走 claude agents --json(官方 bg-fleet 视图),别各自维护会过期的本地账本。
一根三枝
一个 bg session 的足迹 = {bg-spare 进程 · 一套 MCP server · hub-channel.ts→Hub 注册 · ~/.claude/jobs/<id>/state.json · ~/.claude/sessions/<pid>.json}。
session"关掉"时 daemon 不整体拆,反而能 re-adopt / claim-spare 拉回(#8 实证:daemon.log bg adopt: adopted=N、反复 claimed-spare ... (fleet))。于是同一个不死足迹,三个外显:
枝
现有 issue
留下的足迹片段
现修法(各自打补丁)
Hub 一直亮 / ghost 累积
hub #36
hub-channel 还连 → instance-identities.json 永不剔(645 条,85% 无 tag)
Hub GC + ?include= 过滤(未实现,见下 )
"session 还活着" 关不掉
launcher #8
PID 文件 + fleet 成员 → --resume 撞 "running as bg agent",kill 又复活
launcher 读官方 bg 视图
僵死 job 清不掉
launcher #11
jobs/<id>/state:failed 死锁
launcher 扫 jobs/ + 一键送葬
注:另有一类"长命 session 的 MCP server 持 stale 码"现象(2026-05-30 实测,一个对账工具因此差点误删数据),但它的根是 Python import-cache 不 reload + 消费侧缺破坏性护栏,与 bg-fleet 的 adopt/claim-spare 无因果 (fleet 只是把 session 拉长做了放大)。故不并入本 issue ,另行处理(reconcile 类工具加"孤儿占比异常即中止"护栏 + 改码后 /mcp 重连的纪律)。
为什么"单一 oracle"是去重的正解
Hub(#36)和 launcher(#8 )现在各修各的:Hub 自己 GC identities、launcher 自己 kill(pid,0)。但两份账本都会被 daemon 的 auto-adopt/claim-spare 复活弄脏。与其各自维护,不如共用官方权威视图:
版本事实(必须诚实) :#8 写作版本(2.1.143)报告 fleet routine 的 ~/.claude/sessions/*.json kind 仍是 "interactive",故"本地 sentinel 走不通、必须走 oracle"。但 v2.1.154 实测 kind 已是 "bg" ,claude agents --json 也报 "kind":"background"。这反而部分削弱了"必须走 oracle"的论证 —— 一个本地 kind=="bg" sentinel 现在可能就够,不一定要 shell out 到 claude agents。请作者按当前版本复核 :若 kind=bg sentinel 稳定,那是更轻的路;claude agents --json 作为更权威、跨"PID 文件/sentinel 都可能滞后"的兜底。两条都比"各维护本地账本"强。
另:终止 bg session 的官方命令入口待作者确认 —— #8 用户故事提到 claude agents kill <id>,但 v2.1.154 的 claude agents 是零位置参数命令,无 kill 子命令(claude agents kill 报错)。清理动作的官方入口需作者查当前版本(可能改了拼法或走别的命令)。本 issue 不把 claude agents kill 当既成接口 。
一个读码确证的观察:#36 的过滤从未落地
forge-hub #37(CLOSED)与 #36(OPEN)是同一 issue 的两份 (正文逐字相同);#37 被 close 但 fix 没进代码 ,#36 还开着。读 HEAD 确认:
routes/instances.ts::handleInstances() 无任何 query 参数解析 ,永远 buildInstanceList(true) → instance-manager.ts:117 listKnownInstances() 全量 union saved identities 。#36 提议的 ?include=live|tagged 在代码里根本不存在 。
客户端 fh 的 peers/status 调 hubGet("/instances")(无 query),拿回后客户端切分并主动打印 known 段 (已知但不在线: ...)。
双重确定:服务端没 filter + 客户端故意显示 known。2026-05-30 实测 fh hub peers 列 27 个 forge-<pid> ghost("上次出现 13h/19h前")。
→ 即 #36 的提议未实现 ,ghost 仍全量返回。这是三枝里最实、可直接落码的一枝。
不在本 issue 范围
不重复 #36 已提的 instance-identities.json GC / ?include= 具体实现。
不替作者定 launcher 要不要暴露 bg 这一维(background agent session 未与交互 session 区分 — 用户视角"仍在运行"歧义 #8 已把决定权留给作者)。本 issue 只主张:若要修,统一以 claude agents --json(或当前版本的 kind=bg sentinel)作 liveness 真相,比各维护本地账本更省、更不被 daemon 复活打脸。
MCP stale 码那枝(import-cache + reconcile 护栏)不属本 issue,见上注。
[design] bg-session 足迹不死是同一个根:统一用
claude agents --json作 liveness 真相源TL;DR
#8(launcher 看 bg session "仍在运行")、#11(launcher 清不掉僵死 daemon job)、forge-hub #36(/instances无限累积 ghost)——表面三个独立症状,根是同一个:Claude Code 的 bg-fleet daemon 通过auto-adopt+claim-spare把一个 bg session 的"足迹"留住/复活,而没有任何一方对这个足迹做原子拆除。Hub 和 launcher 各自维护一份"谁还活着"的账本(Hub 的instance-identities.json/ launcher 的kill(pid,0)存活判定),两份账本都会被 daemon 的复活机制弄脏。提议:liveness 的真相源统一走
claude agents --json(官方 bg-fleet 视图),别各自维护会过期的本地账本。一根三枝
一个 bg session 的足迹 =
{bg-spare 进程 · 一套 MCP server · hub-channel.ts→Hub 注册 · ~/.claude/jobs/<id>/state.json · ~/.claude/sessions/<pid>.json}。session"关掉"时 daemon 不整体拆,反而能 re-adopt / claim-spare 拉回(#8 实证:daemon.log
bg adopt: adopted=N、反复claimed-spare ... (fleet))。于是同一个不死足迹,三个外显:instance-identities.json永不剔(645 条,85% 无 tag)?include=过滤(未实现,见下)--resume撞 "running as bg agent",kill 又复活jobs/<id>/state:failed死锁为什么"单一 oracle"是去重的正解
Hub(#36)和 launcher(#8)现在各修各的:Hub 自己 GC identities、launcher 自己
kill(pid,0)。但两份账本都会被 daemon 的 auto-adopt/claim-spare 复活弄脏。与其各自维护,不如共用官方权威视图:claude agents --json(实测 v2.1.154 存在)输出 bg session 数组,每条含pid / cwd / kind / sessionId / name / status,机读、不需 TTY。这是"哪些 bg session 真活着"的官方答案。claude agents --json为准,别只信kill(pid,0)(daemon auto-adopt 会让 PID 文件说谎)。claude agents --json、又超 N 分钟没 ping → 判真死 → 剔 identity + 释放它占的锁。一个读码确证的观察:#36 的过滤从未落地
forge-hub #37(CLOSED)与 #36(OPEN)是同一 issue 的两份(正文逐字相同);#37 被 close 但 fix 没进代码,#36 还开着。读 HEAD 确认:
routes/instances.ts::handleInstances()无任何 query 参数解析,永远buildInstanceList(true)→instance-manager.ts:117 listKnownInstances()全量 union saved identities。#36 提议的?include=live|tagged在代码里根本不存在。fh的 peers/status 调hubGet("/instances")(无 query),拿回后客户端切分并主动打印 known 段(已知但不在线: ...)。fh hub peers列 27 个forge-<pid>ghost("上次出现 13h/19h前")。→ 即 #36 的提议未实现,ghost 仍全量返回。这是三枝里最实、可直接落码的一枝。
不在本 issue 范围
instance-identities.jsonGC /?include=具体实现。claude agents --json(或当前版本的kind=bgsentinel)作 liveness 真相,比各维护本地账本更省、更不被 daemon 复活打脸。