From 53bee034caf9d4241751c492d473874ab0500266 Mon Sep 17 00:00:00 2001 From: Yaojia Wang Date: Thu, 30 Jul 2026 12:48:02 +0200 Subject: [PATCH] docs(progress): log the desktop USE_TMUX default fix --- docs/PROGRESS_LOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/PROGRESS_LOG.md b/docs/PROGRESS_LOG.md index 8f09594..99e9565 100644 --- a/docs/PROGRESS_LOG.md +++ b/docs/PROGRESS_LOG.md @@ -40,12 +40,15 @@ - **LOW**:kill/cleanup 丢弃返回值,拒绝(代理下 Origin 不符的 403、竞态的 404)看起来像按钮坏了 —— 改为回显。 - **性能/规模决策**: 预览**每张卡只取一次**(不随 5s 刷新重取),网格**封顶 24 张**(每张缩略图是一个 xterm 实例),超出部分**在 UI 里明说**并给出 `tmux attach -t web_` 兜底 —— 不静默截断。 - **验证**: `tsc` 双 project 干净;单测 **2209 全过**(+42);e2e **27/27**;新增孤儿集成测试 **8/8** 跑真 tmux(建一个一次性 session → 列出 → capture 并断言它**仍是 attached=0** → 拒非 UUID id → 拒外来 Origin → 杀掉 → 第二次 404)。**该集成文件刻意从不发起批量 DELETE** —— 它跑在宿主机真实 tmux server 上,会终结开发者自己的会话。`window_activity` 的验证在**独立 socket(`tmux -L`)**上做,全程未触及用户的 69 个 session,事后确认仍是 69。 -- **遗留 / 待办**: B(启动收养 + 让 `reapIdle` 管到孤儿)未做,需要配"按需 attach"(`Session.pty` 可空)才划算 —— 那要动 Session 形状和不变量 #2。另:桌面 App 默认 `USE_TMUX=0`(`desktop/src/server-config.ts:41`),所以**在 App 里起的 session 不受 tmux 保活**,退出 App 就会被杀;这与"走开再回来"的初衷冲突,已向用户指出,是否改默认值待定。 +- **顺带修掉一个更严重的设计缺陷(用户确认后改)**: 桌面 App 之前把 `USE_TMUX=0` **钉死在所有平台**,不只 Windows。没有 tmux 时 shell 是内嵌 server 的**直接子进程**,于是 `SessionManager.shutdown` 走 kill 分支 —— **退出 App(托盘 Quit、注销、自动更新)会把所有 session 一起杀掉**。这直接否掉了本 App 的立身之本:丢个任务给 Claude Code、走开、换台设备再连上;一个活不过关窗口的 session 做不到这件事。而独立 server 一直是对的(`USE_TMUX` 未设 → `auto` → 探测二进制是否存在)。现改为非 Windows 默认 `auto`,Windows 仍强制关(tmux 是 *nix 的东西),显式 ambient `USE_TMUX` 依然优先,所以 `USE_TMUX=0` 仍是退出开关。 + - **测试断言的是端到端结果而不是那个字符串**:`buildServerEnv` 喂进 `loadConfig` 后 `useTmux === true`(装了 tmux)、显式退出时为 `false`。原来的 bug 正是这两层**对同一个值的含义理解不一致**,只断言字符串是抓不到的。 + - **注意**:改动之前在 App 里起的 session **不会被追溯保护** —— 它们背后本来就没有 tmux session,仍会随 App 一起死。只有装了新包之后新建的才活得下来。 +- **遗留 / 待办**: B(启动收养 + 让 `reapIdle` 管到孤儿)未做,需要配"按需 attach"(`Session.pty` 可空)才划算 —— 那要动 Session 形状和不变量 #2。 - **第二轮对抗式审查又抓到一个安全洞(必须记住)**: **`tmux -t ` 的解析顺序是 精确名 → 名字前缀 → fnmatch**。这直接绕过本模块的核心承诺("只碰 `web_` + UUID v4,用户自己建的 tmux session 永不被枚举/删除"):一个叫 `web__mine` 的 session 被 `parseSessionList` 拒绝(后缀不是 UUID)因此**从不出现在列表里**,但 `-t web_` 会**前缀匹配到它** —— 于是打向一个不存在 id 的 DELETE 会杀掉用户自己的会话,预览会打印它的屏幕。tmux 3.6a 独立 socket 实测:`has-session -t web_` 命中 `web__MINE`,加 `=` 则 `can't find session`;`capture-pane -p -t web_` **打印出了 DECOY 的内容**,`-t =web_:` 则失败。**要两种 target 形态**:`=` 只限定 target 的 session 部分,所以 session target(`has-session`/`kill-session`)用 `=name`,而 `capture-pane` 收的是 **pane** target,裸 `=name` 会被 tmux 直接拒("can't find pane"),必须写 `=name:`。已加集成测试:真的立一个 decoy,断言列表不含它、预览和 DELETE 都 404、且它**依然活着**。 - 另外三条:① 改为 **`max(window_activity, session_activity)`** —— 两个时钟**没有偏序**,`window_activity` 跟输出,而"attach 但没输出"只推 `session_activity`;本机实测有一个 session 的 `session_activity` 比 `window_activity` **新 4 秒**(另一个反过来新 25 天)。问题是"这儿有没有发生过任何事",答案就是两者取晚。② `parseSessionList` 曾接受**空数字字段**:`Number('')` 是 0 且 0 是有限值,于是空时钟被读成"纪元创建、之后一直闲置" → 立刻符合清理条件;改为必须是纯数字。③ 批量清理**杀之前重读一遍世界**:候选列表是快照而循环要花时间,期间 session 可能被 attach 或被收养进表 —— 这是不可逆路径,不信快照。④ `captureOrphan` 去掉了多余的 `hasSession` 预探测:那是每张缩略图**第二次** tmux spawn、而且是**同步**的、还在整格刷新的路径上,而 `capture-pane` 本来就会在 session 不存在时干净失败。护栏拆成纯函数半边(`mayActOnOrphan`:UUID 形状 + 不在表里,两条路共用)和存在性探测(只有毁灭性路径需要)。 - **验证(第二轮)**: 单测 **2213 全过**,孤儿集成 **9/9**(含 decoy 回归)。确认 `has-session -t =` 仍能解析本机 69 个真实 session,**Case 3.5 重连路径未受影响**。 - **已知抖动(非本次引入)**: `test/integration/server.test.ts` 的 "H1 shell state survives a server restart" 在 27 个用例并发跑且机器满载时约 1/3 概率失败,**单独跑 4/4 通过** —— 就是日志里早已记录的真实 PTY 时序抖动。 -- **commit**: `4892fa7`(功能)+ `f6ef19e`(第一轮审查修复)+ `d39a0ab`(第二轮:精确 target + 双时钟),合并 `22a7929`。 +- **commit**: `4892fa7`(功能)+ `f6ef19e`(第一轮审查修复)+ `d39a0ab`(第二轮:精确 target + 双时钟)+ `5833529`(桌面 App tmux 默认值),合并 `22a7929`。 ### 🎨 [x] w6 收尾 · 设计稿逐条对照,补掉 Git 面板的落地偏差(2026-07-29/30,worktree `panel-design-fidelity` + `panel-fidelity-leftovers`)