背景
Claude Code v2.1.139+ 引入 claude agents / /bg 后,session 多了 "bg fleet routine" 这一类。更隐蔽的是:用户即使从未主动跑 /bg,daemon 也会 auto-adopt 历史遗留的 bg session——每次 daemon self-restart(包括 CC 自更新触发的)都会 bg adopt: adopted=N dead=0,把 ~/.claude/sessions/ 里残留的 PID 文件对应的 session 重新拉回 fleet 继续 routine。
Launcher 当前活跃判定(SessionScanner.swift::scanActiveSessions 读 sessions JSON → kill(pid, 0))逻辑没错,但判定的语义在 bg fleet 引入后变多义:绿点既可能是"用户在前台 attach 的 session",也可能是"daemon 在跑 routine"。而 sessions JSON 本身没有 sentinel 字段区分(实测 fleet routine 跑时的 kind 仍是 "interactive",和前台 session 一样)。
用户视角的故事
- 用户关闭了所有 Claude Code UI
- 看到 launcher 菜单栏某个 session 仍间歇性显示绿点
- 点击 →
claude --resume <id> 失败:Session is currently running as a background agent (bg). Use 'claude agents' to find and attach to it
- 用户
kill <pid> → 绿点消失 → 一段时间后 daemon 重新 claim-spare → 又显示绿点
- 真正彻底关掉得跑
claude agents kill <id> —— 但用户不会知道,因为 launcher 不暴露这个维度
这件事独立成立——不依赖任何 daemon 异常或偶发现象。只要 bg fleet 机制存在 + launcher 不读这一维,gap 就在那儿。
已自己机器实证否定的一个方向
我原本以为 ~/.claude/sessions/<pid>.json 在 fleet routine 跑时会写不同的 kind(比如 "background"),launcher 读这个 sentinel 就够了。实测否定:本机 PID 39482 那个 session(daemon.log 显示被反复 bg claimed-spare 732225a2 (fleet) 唤起)写出来的 kind 仍是 "interactive"。这条 zero-cost 的路目前走不通。
几个可能方向(作者决定,不走 PR)
按"破坏 launcher 设计原则的程度"从小到大:
- 如果作者愿意往这个方向走:
claude agents list 是官方 CLI 接口,符合 launcher "只在 CC 官方 CLI 和文件接口上做 UI 层"的原则。launcher 调它来给 fleet session 标 "BG" / "🤖 routine" 角标(纯 enrichment、零交互改变)
- 右键菜单加一项 "Kill background agent..."(背后 spawn
claude agents kill <id>)
- 顶级菜单加 "Background Agents (n)" 子组,展开 fleet——这是 1+2 的菜单结构化版本
关于不走 PR
尊重 launcher README 里"只在 Claude Code 官方 CLI 和文件接口上做 UI 层。不修改 Claude Code 的源代码、不改动会话 JSONL、不接触 Claude Code 内部状态"的设计精神。方向选哪个、要不要做都由作者裁决。这个 issue 只把现象和痛点摆清楚。
环境
- Claude Code: 2.1.143
- forge-launcher: 当前 main(本地装 v0.1.1 后再升)
- macOS 26 / Darwin 25.5.0
背景
Claude Code v2.1.139+ 引入
claude agents//bg后,session 多了 "bg fleet routine" 这一类。更隐蔽的是:用户即使从未主动跑/bg,daemon 也会 auto-adopt 历史遗留的 bg session——每次 daemon self-restart(包括 CC 自更新触发的)都会bg adopt: adopted=N dead=0,把~/.claude/sessions/里残留的 PID 文件对应的 session 重新拉回 fleet 继续 routine。Launcher 当前活跃判定(
SessionScanner.swift::scanActiveSessions读 sessions JSON →kill(pid, 0))逻辑没错,但判定的语义在 bg fleet 引入后变多义:绿点既可能是"用户在前台 attach 的 session",也可能是"daemon 在跑 routine"。而 sessions JSON 本身没有 sentinel 字段区分(实测 fleet routine 跑时的kind仍是"interactive",和前台 session 一样)。用户视角的故事
claude --resume <id>失败:Session is currently running as a background agent (bg). Use 'claude agents' to find and attach to itkill <pid>→ 绿点消失 → 一段时间后 daemon 重新 claim-spare → 又显示绿点claude agents kill <id>—— 但用户不会知道,因为 launcher 不暴露这个维度这件事独立成立——不依赖任何 daemon 异常或偶发现象。只要 bg fleet 机制存在 + launcher 不读这一维,gap 就在那儿。
已自己机器实证否定的一个方向
我原本以为
~/.claude/sessions/<pid>.json在 fleet routine 跑时会写不同的kind(比如"background"),launcher 读这个 sentinel 就够了。实测否定:本机 PID 39482 那个 session(daemon.log 显示被反复bg claimed-spare 732225a2 (fleet)唤起)写出来的kind仍是"interactive"。这条 zero-cost 的路目前走不通。几个可能方向(作者决定,不走 PR)
按"破坏 launcher 设计原则的程度"从小到大:
claude agents list是官方 CLI 接口,符合 launcher "只在 CC 官方 CLI 和文件接口上做 UI 层"的原则。launcher 调它来给 fleet session 标 "BG" / "🤖 routine" 角标(纯 enrichment、零交互改变)claude agents kill <id>)关于不走 PR
尊重 launcher README 里"只在 Claude Code 官方 CLI 和文件接口上做 UI 层。不修改 Claude Code 的源代码、不改动会话 JSONL、不接触 Claude Code 内部状态"的设计精神。方向选哪个、要不要做都由作者裁决。这个 issue 只把现象和痛点摆清楚。
环境