Skip to content

fix(role-switch): 角色切换从会话内注入 /cd 改为带 --resume 的进程 respawn#507

Open
xu4wang wants to merge 1 commit into
deepcoldy:masterfrom
xu4wang:fix/role-switch-respawn
Open

fix(role-switch): 角色切换从会话内注入 /cd 改为带 --resume 的进程 respawn#507
xu4wang wants to merge 1 commit into
deepcoldy:masterfrom
xu4wang:fix/role-switch-respawn

Conversation

@xu4wang

@xu4wang xu4wang commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

问题

Claude 家族 CLI 的系统上下文(CLAUDE.md、记忆路径/索引)是开场按启动 cwd 注入一次的静态快照,会话内 /cd 只改 cwd、不重刷这份注入(CLI 原生行为,botmux 无独立修复点)。当前角色切换走「向活 CLI 注入 /cd」(进程不重启),后果是切换后模型仍拿旧角色的记忆索引读写:读旧索引(新角色表现为"没记忆")、按旧路径写记忆进错桶。实测验证:/cd 后的系统提示只报 cwd 变更,模型手里的记忆路径仍是开场那份冻结快照(甚至指向已不存在的目录)。

修法

「换角色外壳、留对话内核」:cd IPC 路由对活 worker 不再注入 /cd,改发带 updateWorkingDirrestart 消息;worker 先把 lastInitConfig.workingDir 收敛到新目录,再走既有 restart 机制(杀 CLI + {...lastInitConfig, resume: true} respawn)。--resume 回放对话历史保留上下文;新 cwd 冷启动让新角色的 CLAUDE.md/记忆索引在开场注入。respawn 与 /restart 同机制、适配器无关,supportsSessionCwdMove 能力位分流随之退场(字段保留,其它路径未动)。

worker 的 restart case 配套三件事(对抗式复验驱动):

  • restart 合并:已有 restart 在飞(teardown 中 / tmux jitter 定时器未触发)时不叠加第二轮,只收敛 workingDir——叠加会 clearTimeout 吃掉首轮 teardown、把重启预算无故烧到 tier-2 强制 FRESH(丢上下文),非 tmux 路径还会双 spawn。pending 的 spawn 展开 {...lastInitConfig} 时自然拿到新目录。
  • cwd-move respawn 不计入 consecutiveInWorkerRestarts:用户主动迁移不是崩溃恢复,计入会让「切角色 + 一次无关重启」无故强制 FRESH;respawn 真失败仍有 claude_exit → daemon auto-restart 裸 restart 的计数兜底,回路有界。
  • 复位在飞的 durable turn(对齐 expire_durable_turn 分支):否则会议场景(durable turn 中执行切换)respawn 后 durableTurnInFlight 残留 true,所有消息卡到 lease 过期、daemon 还会把刚 respawn 的新 CLI 二次 teardown。此修复同时覆盖普通 /restart 的同族预存在问题。

顺带收掉 inject_command.updateWorkingDir 死通道:唯一发送方已不存在,留着是一个绕过 cd 路由角色库硬校验(validateRoleLibraryPath)的免检 cwd 移动口。/slash 路由测试已断言 not.toHaveProperty('updateWorkingDir') 把封口写进契约。

⚠️ 部署耦合(合并 gate)

本 PR 必须晚于或与 #506(xu4wang:fix/resume-cross-bucket) 同时合并/部署。 respawn 后 resume 探的是新 cwd 桶,旧桶 transcript 必须由该 PR 的跨桶迁移(rescueOrphanClaudeTranscript)接住;单独上线本 PR = claude-code 每次切角色 100% 触发「无法恢复」丢上下文(比现状更糟)。

测试

  • test/ipc-cd-route.test.ts 全分支矩阵更新:mode:respawn-resume(restart 携带校验产出的 resolvedPath、repin 先于 send、不 kill、initConfig 同步)、send 抛错回落 cold-restart、无 worker 无条件 killWorker(清残留 pane)、codex/未知 cliId 一视同仁 respawn。相关套件(cd 路由/slash 路由/注入队列策略/slash-inject)48 passed。
  • 全量 unit 套件与基线对比零回归(失败文件均为基线内环境依赖项)。
  • 真机最坏场景(真 claude 2.1.210):Bash 工具飞行中 kill -9(transcript 尾悬空 tool_use,模拟 bot 自己在 turn 中发起 botmux cd 被杀的时序)→ 跨桶迁移 → 新目录 --resume 完整续回上下文。

已知项(不阻塞)

  • gemini 等 buildArgs 不支持 resume 的适配器,respawn 实为全新会话(与旧 cold-restart 行为一致、无回归),但 respawn-resume 文案对其是过度承诺,后续可按适配器能力降级文案。
  • durable turn 中切换后,receipt 对账仍等 lease 过期走 withhold-ACK 升级(严格不劣化于现状且解除了卡死);彻底消除需在 restart 时对在飞 attempt 发 emitTurnTerminal('ambiguous'),建议另开 issue。

🤖 Generated with Claude Code

@xu4wang
xu4wang requested a review from deepcoldy as a code owner July 16, 2026 22:54
CLI 的系统上下文(CLAUDE.md、记忆路径/索引)是开场按启动 cwd 注入一次
的静态快照,/cd 只改 cwd 不重刷——注入式切换后模型仍拿旧角色的记忆索引
读写:读旧索引(新角色"没记忆")、写新记忆进错桶。记忆注入是 Claude
原生行为,botmux 无独立修复点,只能让"开场"在新 cwd 重新发生。

改法:cd 路由对活 worker 不再注入 /cd,改发带 updateWorkingDir 的
restart——worker 收敛 lastInitConfig.workingDir 后走既有 restart 机制
(杀 CLI + {...lastInitConfig, resume: true} respawn)。--resume 回放
对话历史保留上下文,新 cwd 冷启动让新角色 CLAUDE.md/记忆索引开场注入,
"换角色外壳、留对话内核"。respawn 与 /restart 同机制、适配器无关,
supportsSessionCwdMove 能力位分流随之退场(字段保留,其它路径未动)。

worker 的 restart case 配套三件事:
- restart 合并:已有 restart 在飞(teardown 中/tmux jitter 未触发)时
  不叠加第二轮,只收敛 workingDir——叠加会吃掉首轮 teardown、烧重启
  预算到 tier-2 强制 FRESH,非 tmux 路径还会双 spawn。
- cwd-move respawn 不计入 consecutiveInWorkerRestarts:用户主动迁移
  不是崩溃恢复,计入会让"切角色+一次无关重启"无故强制 FRESH 丢上下文;
  respawn 真失败仍有 claude_exit → auto-restart 的计数兜底。
- 复位在飞的 durable turn(对齐 expire_durable_turn),否则会议场景切
  角色后消息全卡到 lease 过期、新 CLI 被二次 teardown。

顺带收掉 inject_command.updateWorkingDir 死通道:唯一发送方已不存在,
留着是一个绕过 cd 路由角色库硬校验的免检 cwd 移动口。

⚠️ 部署耦合:本补丁的 respawn 后 resume 探的是新 cwd 桶,旧桶
transcript 必须由 fix(claude) resume 预检跨桶迁移
(rescueOrphanClaudeTranscript)接住。两个修复必须一同部署,
单独上线本补丁 = claude-code 切角色 100% 触发"无法恢复"丢上下文。

已验证:路由/注入/队列策略 48 测全过;真机最坏场景(工具飞行中
kill -9 留悬空 tool_use 尾部)+ 跨桶迁移 + 新目录 --resume 上下文完整。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

--- codex review 加固(2026-07-17)---
- durable 回执:切角色 restart 杀掉在飞的 durable turn 时,主动 emitTurnTerminal(
  'ambiguous')——CLI 中途被杀、副作用是否已发无法证明(故非 'cancelled'),交 daemon
  重试策略即时对账,取代原先「等 lease 过期 → expire_durable_turn 无法证明 → 扣 ACK →
  超时 fencing teardown」的慢路径。复用 emitTurnTerminal 的释放/去重;其 flushPending
  微任务因 restartCliProcess 同步置位 cliRestartInProgress 而空跑(已核)。
@xu4wang
xu4wang force-pushed the fix/role-switch-respawn branch from c1d0213 to 2f28ea5 Compare July 17, 2026 03:55
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