Skip to content

feat(backend): 兼容 rcfile exec 切换 shell 导致会话起不来的场景#314

Merged
deepcoldy merged 2 commits into
masterfrom
wt/claude-3
Jun 26, 2026
Merged

feat(backend): 兼容 rcfile exec 切换 shell 导致会话起不来的场景#314
deepcoldy merged 2 commits into
masterfrom
wt/claude-3

Conversation

@deepcoldy

Copy link
Copy Markdown
Owner

背景

用户(江威峰)配置 botmux + Codex 后会话起不来:首条多行 prompt 落进裸 zshzsh: parse error near '\n',手敲 codex 才能 work。

根因:用户 $SHELL=/bin/bash,但 ~/.bashrc 里有 [ -t 1 ] && exec zsh(chsh 失败的 hack)。botmux 在 tmux pane 用 <$SHELL> -i -c '… exec /usr/bin/env <cli>' 启动 CLI(tmux-pipe-backend.ts:createDetachedSession)——-i 会 source .bashrc,pane stdout 是 tty → exec zsh-c body 跑到之前就顶替 bash,CLI 的启动命令永远没机会执行,pane 沦为裸 zsh。

经分析对四种方案做了对抗性压测(含真机验证)。关键约束:tmux new-session 的 pane 不继承 client env,只继承 server 全局 env——所以"抓 rc env 再塞回去"的透明兼容(L1)雷区最多,本 PR 按申晗决定跳过 L1,落 L2 + L3。

改动

L2 · 裸壳检测安全网(worker.ts + session-discovery.ts

打首条 prompt 前,比对 pane leaf 进程 comm 是否为裸壳(bash/zsh/sh/dash/ash/…)。是则不把多行 prompt 打进去,改发一张飞书诊断卡片,按 $SHELL vs leaf 是否一致分两档点名根因(trampoline / 启动慢·CLI 没找到)+ 修法。

  • 检测点 = flushPending 首刷处:首条 prompt 本就被 hold 到 ready 或 15s/45s 超时,届时健康 CLI 早已 exec,只有 trampoline/失败才仍是裸壳 → 误报率极低。
  • 跳过 wrapperCli(launcher 合法套壳)/ adoptMode(观察既有 pane);pty/herdr 直接 exec CLI,leaf 永不是裸壳,天然不触发。
  • 必在 runStartupCommands 之前跑(否则 startupCommands 会先被打进裸壳)。

L3 · per-bot launchShell 覆盖

新增 BotConfig.launchShell(shell 名或绝对路径),覆盖 $SHELL 启动 CLI,直接绕开会跳转的 rc。

  • 贯通 init payload → SpawnOpts → resolveUserShell(env, override),三处 call site(tmux-pipe / zellij / 旧 tmux backend)。
  • 两套配置面:/config launchShell(CONFIG_FIELDS)+ dashboard「机器人默认设置 → 启动 Shell」,写盘统一走 applyConfigField
  • ⚠️ 注意:PATH/nvm/pnpm 要放进所选 shell 的 rc——已在 hint/docs 标注。

注:workflow-resume / daemon-spawn 两条 init 构造默认 backendType:'pty'(无 shell wrapper,trampoline 免疫),不需要 launchShell,未线程进去。

验证

  • 全量单测 375 文件 / 6374 例通过;新增 resolveShellOverride+resolveUserShell override 5 例、isBareShellComm 4 例。
  • 真实 tmux pane 端到端:植入 .bashrc=[ -t 1 ] && exec zsh 的 fake HOME(-e HOME 注入 pane env)——
    • bug 变体(bash -i):pane leaf=zsh、fakecli 未启动 ✓ 复现
    • fix 变体(zsh -l -i,launchShell=zsh):fakecli 正常拉起
  • 文档:bots-json.md 配置表加 launchShell(中英同步)。

Review 关注点(供 @codex

  • L2 误报/漏报:检测时机、wrapperCli/node 启动 CLI(comm=node)、themed prompt false-ready
  • L3 三处 call site 是否齐全、配置两面(/config + dashboard)写盘一致性
  • 诊断卡片文案是否会误导(slow-rc vs trampoline 分档)

@chatgpt-codex-connector

Copy link
Copy Markdown

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

修复用户登录 $SHELL(如 bash)的 rc 文件里有 `exec zsh` 之类跳转时,
botmux 用 `<$SHELL> -i -c '… exec /usr/bin/env <cli>'` 在 tmux pane 里
启动 CLI——`-i` 会 source `.bashrc`,`exec zsh` 在 `-c` body 跑到之前
就把 shell 顶替掉,CLI 的启动命令永远没机会执行,pane 沦为裸 zsh,
首条多行 prompt 被打进去报 `zsh: parse error near '\n'`,会话假死。

两层修复(不动现有 wrapper 启动形态,零回归面):

- L2 裸壳检测安全网:打首条 prompt 前,比对 pane leaf 进程 comm 是否为
  裸壳(bash/zsh/sh/…)。是则不把多行 prompt 打进去,改发一张飞书诊断
  卡片,点名最可能的 rc trampoline 根因 + 两种修法。检测点选在 flushPending
  首刷处——首条 prompt 本就被 hold 到 ready 或 15s/45s 超时,届时健康 CLI
  早已 exec,只有 trampoline/启动失败才仍是裸壳,误报率极低。跳过
  wrapperCli(launcher 合法套壳)/ adopt(观察既有 pane)。把「排查一晚上」
  压成卡片上一眼可见的根因。

- L3 per-bot launchShell 覆盖:新增 BotConfig.launchShell,指定启动 CLI
  用的 shell(zsh|bash|sh 或绝对路径),覆盖 $SHELL,直接绕开会跳转的 rc。
  贯通 init payload → SpawnOpts → resolveUserShell(env, override)(tmux-pipe
  / zellij / 旧 tmux backend 三处 call site)。两套配置面:/config launchShell
  + dashboard「机器人默认设置 → 启动 Shell」,写盘走统一 applyConfigField。

测试:resolveShellOverride / resolveUserShell 覆盖 5 例、isBareShellComm 4 例;
真实 tmux pane 端到端验证:bug 变体落裸 zsh、launchShell=zsh 变体正常拉起 CLI。
全量单测 375 文件 6374 例通过。文档:bots-json 表加 launchShell(中英)。
Codex review 指出:原注释称 reattach 跳过裸壳检测,但实现只重置
bareShellLaunchBlocked,检测实际靠 !hasRunStartupCommands 间接 gate。
而 reattach 时 hasRunStartupCommands=true → 检测被跳过,若 daemon 重启
复用到一个已退化成裸壳的持久 pane,首条消息会被直接打进 shell(正是本
PR 要消除的 bug)。

改为独立一次性 gate bareShellChecked:检测在每个 spawn(含 reattach)的
首刷只跑一次,仅当 leaf 确为裸壳才触发——健康 reattach(leaf=存活 CLI)
自动不命中,退化 pane 则正确弹诊断卡而非打进 shell。注释同步纠正。

另把诊断分档抽成纯函数 bareShellLaunchKind(leafComm, expectedShell)
(session-discovery.ts)并补 3 个单测,回应 Codex「补个小测试」建议。
全量单测 375 文件 / 6377 例通过。
@deepcoldy deepcoldy merged commit 939347c into master Jun 26, 2026
1 check passed
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