Skip to content

fix(restore): 机器重启后托管会话不再被误判僵尸清除——missing 一律保留 lazy cold-resume - #705

Open
deepcoldy wants to merge 1 commit into
masterfrom
fix/restore-never-zombie-close-managed-sessions
Open

fix(restore): 机器重启后托管会话不再被误判僵尸清除——missing 一律保留 lazy cold-resume#705
deepcoldy wants to merge 1 commit into
masterfrom
fix/restore-never-zombie-close-managed-sessions

Conversation

@deepcoldy

Copy link
Copy Markdown
Owner

问题

机器重启后,重启前正活跃的飞书会话无法自动 resume——它们被永久 closeSession 了,话题从看板消失、下条消息也不再 lazy-resume。

根因:默认 tmux 后端的 bmx-* 会话与 operator 自己终端共享默认 tmux socketensure-tmux.ts 注释明说 "Production intentionally targets the default server")。

  • daemon:restart 正常:tmux server 独立于 daemon 存活 → pane 还在 → 重连活着的 CLI。
  • 机器重启:tmux server 连同所有 pane 一起没了。restore 本该走「整个 server down(reboot)→ 保留会话等 cold-resume」。但 operator 终端在 daemon restore 之前复活了那个共享 default server → TmuxBackend.serverState() 返回 'running' 而非 'down' → 守卫被击穿 → 每个 missing 的 pane 被误判成 solo zombie → closeSession 永久关闭。

live 日志实测(一次真实 reboot):僵尸关闭 239 个活跃会话,正确「server down 保留」仅 1 个(还是 zellij——每会话独立 server 才没被共享 socket 骗到)。最讽刺的是被杀的都是正在用的活跃会话,而闲置被内存回收挂起(suspendedColdResume)的老会话反而走另一分支活了下来。

修复(方案 B:托管会话永不自动僵尸关闭)

restore 探到持久后端 backing missing不再 close,一律保留 worker-less active 记录,交给下条消息 cold-resume:forkWorker(resume=true);若 transcript 真没了,worker 侧已有的 resume→fresh 兜底worker.ts tier-1/tier-2)会自动新起干净会话 + 发用户可见提示「历史会话无法恢复,已新起」——永不卡死

这让 tmux/zellij 对齐既有的 pty(无持久后端,从不探测/关闭)与 zmx(missing 恒 keep)行为。判断依据不再依赖「共享 server 是否存活」这个被 co-tenant 污染的信号,从根本上消除竞态。

离开 active 集合的仅剩三种,均与 backing 存活正交、逻辑不变:

  1. /close——restore 前就已置 status:'closed',根本不进这个 active 分支;
  2. adopt 目标已退出——adopt 分支单独处理(那是包外部进程、自己无 transcript);
  3. CLI 配置切换 mismatch——下方 closeActiveSessionIfCliMismatch 处理。

为什么不需要 TTL 回收

dormant 会话无 worker、无 pane,仅剩磁盘一行 + dashboard 一行,近零资源占用(申晗确认)。故不引入 TTL。若将来需要 dashboard 整洁,应另按 lastMessageAt 做解耦的 dormancy 回收,与本 bug 无关。

影响面(多 CLI × 多后端横向排查)

  • 跨后端
    • tmux / zellij:行为改变——不再 close-on-missing(这正是修复点)。
    • zmx / pty:本就如此,零变化。
    • herdr:之前 serverState 返回 'unknown' → 落到 close 分支;现在同样保留(更安全,且 herdr 本就有 warm-up 慢启动,之前那条路径反而可能误杀)。
  • 死代码清理:移除 probePersistentBackendServer()TmuxBackend / ZellijBackend / ZmxBackend 三个 serverState() 静态方法。它们本就是 commit b260f405 为这个被推翻的 reboot-gate 而加的,现零调用;非 backend 接口契约成员(不在 types.ts/capabilities.ts),删除安全。保留只会诱使未来有人把坏 gate 重新接回来。
  • 跨平台:纯 Linux daemon 路径逻辑,无平台特异改动。

验证

  • pnpm build 通过(tsc + dashboard bundle + audit 全绿)。
  • test/restore-zombie-close.test.tsmissing→close 断言翻转missing→keep;新增/改写「reboot 全保留」「cap-suspend 保留」覆盖;删除已失效的 serverState 脚手架与「unknown→保守 close」用例(现 unknown 也 keep)。25 tests 绿
  • test/tmux-probe.test.ts:删除 serverState describe 块(7 绿)。
  • test/session-resume.test.ts:删除死 serverState stub(33 绿);其原有「不 zombie-close adopt/woken 会话」断言在新行为下更强、仍绿。
  • 全量 vitest run 通过(exit 0,无 fail marker)。

净 diff -122 行:修复方式是移除被击穿的机器,而非再加一层守卫。

base=master fe594fc8b(直接基于,merge-base 干净)· head 1190abea1

## 问题
机器重启后,重启前正活跃的飞书会话无法自动 resume。根因:默认 tmux
后端的 bmx-* 会话与 operator 自己终端共享默认 tmux socket。reboot 后
operator 终端先于 daemon restore 复活了共享 server,导致 restore 里的
`serverState()==='down'`(本用于区分「整机重启」vs「单 pane 僵尸」)判成
`'running'` → 每个 missing 的 pane 被误判成 solo zombie → closeSession
永久关闭。live 日志实测:一次 reboot 关掉 239 个活跃会话,仅 1 个 zellij
(每会话独立 server)逃过。

## 修复(方案 B:托管会话永不自动僵尸关闭)
restore 探到持久后端 backing `missing` 时不再 close,一律保留 worker-less
active 记录,交给下条消息 cold-resume(forkWorker(resume=true);transcript
真没了则 worker 侧 resume→fresh 兜底新起干净会话 + 用户可见提示,永不卡死)。
这让 tmux/zellij 对齐既有的 pty(无持久后端从不探测)与 zmx(missing 恒
keep)行为。

判断依据不再依赖「共享 server 是否存活」这个被 co-tenant 污染的信号,从根本
上消除竞态。离开 active 集合的仅剩三种:真 `/close`(restore 前就已置
closed,根本不进本分支)、adopt 目标已退出(adopt 分支处理)、CLI 配置切换
mismatch(下方分支处理,与 backing 存活正交)。

## 影响面
- 跨后端:tmux/zellij 行为改变(不再 close-on-missing);zmx/pty 本就如此,
  无变化;herdr 之前走 serverState='unknown'→close,现在同样保留。
- 死代码清理:移除 `probePersistentBackendServer()` 及 TmuxBackend/
  ZellijBackend/ZmxBackend 三个 `serverState()`(本就是为这个被推翻的
  reboot-gate 而加,现零调用;非 backend 接口契约成员,删除安全)。
- 未新增 TTL 回收:dormant 会话无 worker、无 pane,仅剩磁盘行 + dashboard
  行,近零资源占用,无需回收(如需 dashboard 整洁另按 lastMessageAt 解耦处理)。

## 验证
- pnpm build 通过(tsc + dashboard bundle + audit 全绿)
- test/restore-zombie-close.test.ts:missing→close 断言翻转为 missing→keep,
  新增 reboot 全保留 + cap-suspend 保留覆盖(25 tests 绿)
- test/tmux-probe.test.ts 去掉 serverState 块(7 绿)、session-resume.test.ts
  去掉 serverState stub(33 绿)
- 全量 vitest run 通过(exit 0,无 fail marker)
@deepcoldy

Copy link
Copy Markdown
Owner Author

@codex 请复审本 PR(head 钉 1190abea1eaef6d6f472304c0c2a01036bb85644)。

一句话:修机器重启后飞书会话不自动 resume——共享默认 tmux socket 被 operator 终端复活,骗过 serverState()==='down' 重启守卫,导致 restore 把活跃会话 mass-close(live 实测一次 reboot 关 239 个)。改法:托管后端 backing missing 一律保留 lazy cold-resume,永不自动 close,对齐 pty/zmx;顺带删掉被推翻的 serverState() 死代码。

建议重点核

  1. 正确性missing 不再 close 后,是否有路径依赖「restore 会清掉 missing 会话」?我已核 CLI-mismatch 分支(正交、仍 close)、adopt 目标退出(adopt 分支单独处理)、真 /close(restore 前已置 closed 不进本分支)。
  2. 死代码删除安全性probePersistentBackendServer + 三个 backend serverState() 是否真无其它生产调用方 / 非接口契约成员。我 grep 确认仅本 restore 分支 + 测试引用。
  3. 跨后端回归:herdr 之前走 serverState='unknown'→close,现在改成保留——是否有 herdr 特有的「僵尸 pane 必须 restore 时清」的需求被我漏掉。
  4. 测试翻转是否 vacuousmissing→keep 断言是否真断到「map 里还在 + store 仍 active + 没 forkWorker」三态。

全量 vitest exit 0;pnpm build 绿。net -122 行。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant