Skip to content

fix(adopt): tmux 接管场景修复真实 CLI PID 解析 + worker 重启保住 bridge(替代 #293) - #608

Merged
deepcoldy merged 3 commits into
masterfrom
fix/adopt-tmux-launcher-claude-pid
Jul 28, 2026
Merged

fix(adopt): tmux 接管场景修复真实 CLI PID 解析 + worker 重启保住 bridge(替代 #293)#608
deepcoldy merged 3 commits into
masterfrom
fix/adopt-tmux-launcher-claude-pid

Conversation

@deepcoldy

Copy link
Copy Markdown
Owner

背景

替代并关闭 #293#293 把 5 个问题打包在一个 WIP 分支,且被 reviewer 拦下若干点(改了 findUniqueClaudeSessionByCwd 歧义返回致红测试、文档评论双通道噪声、硬编码 ttadk env)。本 PR 用干净实现重做其中仍在 master 上真实存在的两条,规避原 reviewer 的所有拦点,并补上原 PR 缺失的测试。

#293 的 5 条经逐一在当前 master 核实:

改了什么

commit 1 — fix(adopt): tmux 接管 launcher 包装的 claude 时解析真实 CLI 子 pid(核心 #1

现象:用 tmux /adopt 接管一个「被 launcher 包一层」(node/ttadk/aiden 等)启动的 Claude Code 会话时,CLI 回复无法返回飞书。

根因链(已在 master 复现):

  1. 进程树 tmux → node <wrapper> → claude(真实);真实 claude comm=claude,wrapper 的 argv 里带 "claude" 字面量。
  2. discoverAdoptableSessionsfindCliProcess 先按 argv 命中 wrapper 层 pidmatchedByComm=false)。
  3. 原代码只对 cliId==='codex' 解析真实子 pid,claude-code 不做 → 记 wrapper pid。
  4. readClaudeSessionMeta(wrapperPid) 读不到(session JSON 按真实 claude 子进程 pid 存)→ sessionId=undefined
  5. daemon 侧 bridgeJsonlPath 只在有 sessionId 时算得出;worker 侧 claude adopt 分支只有 bridgeJsonlPath 才起 transcript bridge(不像 codex/traex/cursor 有 pid 兜底)→ bridge 不启动 → 回复回不来。

修复

  • session-discovery.ts:真实子 pid 解析从 codex-only 放开到所有 !matchedByComm(argv-matched launcher)的 CLI,复用现成 matchedByComm 标志。codex 行为字节级不变;matchedByComm=true 保持 match.pid;找不到子进程回落 match.pid
  • worker-pool.tsforkAdoptWorker 里 claude 的 findUniqueClaudeSessionByCwd cwd 兜底从 herdr-only 放开到所有 adopt 来源(tmux 也走)。未改 findUniqueClaudeSessionByCwd 的歧义返回语义(保持 return undefined,原 feat(adopt): tmux adopt 模式下 bridge 初始化修复 + 文档评论重复回复修复 #293 红测试仍绿)。

commit 2 — fix(adopt): worker 退出后按 adoptedFrom 走 forkAdoptWorker 重启,不丢 bridge 语义(#3

现象:adopt 会话的 bridge worker 退出后(CLI 崩溃,或「adopted session ended」kill 路径),daemon 的 worker-null 重启分支(handleThreadReply / handleDocComment)无条件走 forkWorker → 起全新 bmx-* CLI、丢 observe/bridge 语义、把 <user_message> 裹的 prompt 怼进新 CLI 而非用户原本的外部 pane。idle-worker-sweeper.ts 有注释记录此坑(靠「永不 suspend adopt」绕过,但崩溃/退出路径没兜住)。

修复

  • worker-pool.tsforkAdoptWorker 接受 { prompt?, turnId? } 并透传进 init(原硬编码 prompt: '')。init handler 把 prompt 入队 pendingMessages,adopt idle 检测(setupAdoptIdleDetection → markPromptReady)在观察到 pane 空闲时冲刷到 pane —— 与 live-worker follow-up 完全同路。
  • daemon.ts:两处 worker-null 分支按 ds.adoptedFrom 分流到 forkAdoptWorker。内容已由 buildReforkCliInput / buildDocCommentTurnInput(mode:'refork')buildBridgeInputContent 做成 bridge raw 格式,不会漏 XML 进用户 CLI。

影响面

  • 跨 CLI:commit 1 仅影响 /adopt 发现路径。codex 路径字节级不变(已过 codex-coco-pid smoke);其它 20+ CLI 中只有「comm 落在 COMM_ARGV_LAUNCHERS、靠 argv 命中」的才新走子 pid 解析,正是需要修的场景。
  • 跨后端:tmux 为主;herdr 已有同款 cwd 兜底,本次让 tmux 对齐。zellij adopt 走 originalCliPid 校验,不受影响。
  • 跨平台findLaunchedCliPid / readComm 已是 Linux(/proc) + macOS(ps) 双实现。
  • daemon 路由:commit 2 仅改 worker-null 分支;live-worker 分支本就正确走 sendWorkerInput bridge 路径。对已退出 target 重启靠 worker observe backend 的 onExit → claude_exit 优雅收尾(TmuxPipeBackend.spawn 抛错也在 worker spawnCli 的 try/catch 内,不崩 daemon)。restore 路径 prompt 缺省 '',行为不变。

测试

  • 新增 test/adopt-tmux-claude-wrapper-repro.smoke.test.ts(真实进程冒烟,起真 tmux pane + node→假 claude 子进程 + 按真实 pid 落 session JSON,调生产 discoverAdoptableSessions):
    • WRAPPED(复现 bug)+ DIRECT(回归保护)。去掉修复→WRAPPED 失败、DIRECT 通过;加上修复→两者都过。tmux 不可用时自动 skip。
  • 新增 test/session-lifecycle-start.test.ts 中 issue fix(cli): 修复 claude-code 在 root 账户下启动失败 #3 两测:init 正确透传 {prompt,turnId}(去掉透传→失败);restore 路径缺省 prompt=''
  • pnpm build 通过(tsc 干净)。
  • 回归面全绿:daemon/adopt/session/worker-pool/doc-comment/bridge/lifecycle/discovery/codex-app 共 1676 tests passed(110 文件)。
  • 全量 unit 里另有 20 个失败经核实是环境问题(v3-workflow 进程 fence / /etc sandbox 视图、以及依赖 dist 的 CLI 测试),把本 PR 改动 stash 掉后在干净 master 上一模一样失败,与本次无关。

🤖 Generated with Claude Code

deepcoldy and others added 2 commits July 26, 2026 15:47
## 问题
用 tmux `/adopt` 接管一个「被 launcher 包一层」(node/ttadk/aiden 等)启动的
Claude Code 会话时,CLI 的回复无法返回飞书。

## 根因链(已在 master 上复现)
1. 进程树 `tmux → node <wrapper> → claude(真实)`;真实 claude 的 comm=claude,
   但 wrapper 的 argv 里带 "claude" 字面量。
2. `discoverAdoptableSessions` 的 findCliProcess 先按 argv 命中 **wrapper 层的
   pid**(matchedByComm=false)。
3. 原代码只对 `cliId==='codex'` 做「真实子 pid 解析」,claude-code 不做 → 记录
   wrapper pid。
4. `readClaudeSessionMeta(wrapperPid)` 读不到(session JSON 按真实 claude 子进程
   pid 存)→ sessionId=undefined。
5. daemon 侧 bridgeJsonlPath 只在有 sessionId 时算得出;worker 侧 claude 分支
   **只有 bridgeJsonlPath 才起 transcript bridge**(不像 codex/traex/cursor 有 pid
   兜底)→ bridge 不启动 → 回复回不来。

## 修复
- `session-discovery.ts`:把「launcher 包装下解析真实 CLI 子 pid」从 codex-only
  放开到所有 argv-matched 的 CLI(复用现成的 matchedByComm 标志)。codex 行为
  完全不变(两者都要求 !matchedByComm);matchedByComm=true 保持 match.pid 不变;
  找不到子进程时回落 match.pid。
- `worker-pool.ts`:forkAdoptWorker 里 claude 的 cwd 兜底从 herdr-only 放开到所有
  adopt 来源(tmux 也走),作为双保险。**未改** findUniqueClaudeSessionByCwd 的
  歧义返回语义(保持 return undefined,原 PR293 被 reviewer 拦下的红测试仍绿)。

## 影响面
- 跨 CLI:仅影响 /adopt 发现路径。codex 路径字节级不变(已过 codex-coco-pid
  smoke);其它 20+ CLI 中只有「comm 落在 COMM_ARGV_LAUNCHERS、靠 argv 命中」的
  才新走子 pid 解析,正是需要修的场景。
- 跨后端:tmux 为主;herdr 已有同款兜底,本次让 tmux 对齐。
- 跨平台:findLaunchedCliPid / readComm 已是 Linux(/proc)+macOS(ps) 双实现。

## 测试
- 新增真实进程冒烟测试 test/adopt-tmux-claude-wrapper-repro.smoke.test.ts:
  WRAPPED(复现 bug)+ DIRECT(回归保护)。去掉修复→WRAPPED 失败、DIRECT 通过;
  加上修复→两者都过。
- pnpm build 通过(tsc 干净)。
- 受影响路径全绿:adopt/discovery/session/daemon 共 165 tests passed。

Co-Authored-By: Claude <noreply@anthropic.com>
## 问题(PR#293 issue #3,master 上真实存在)
adopt 会话的 bridge worker 退出后(CLI 崩溃,或「adopted session ended」kill
路径),daemon 的 worker-null 重启分支(handleThreadReply / handleDocComment)
无条件走 forkWorker → 起一个全新的 botmux 托管 bmx-* CLI、丢掉 observe/bridge
语义,把 `<user_message>` 裹的 prompt 怼进新 CLI 而非用户原本的外部 pane。
idle-worker-sweeper 里有注释明确记了这个坑(靠「永不 suspend adopt」绕过,但
崩溃/自然退出路径没兜住)。

## 修复
- `worker-pool.ts`:forkAdoptWorker 接受 `{ prompt?, turnId? }` 并透传进 init
  (原来硬编码 prompt: '')。init handler 会把 prompt 入队 pendingMessages,
  adopt 的 idle 检测(setupAdoptIdleDetection → markPromptReady)在观察到 pane
  空闲时冲刷到 pane —— 与 live-worker follow-up 完全同路。
- `daemon.ts`:handleThreadReply / handleDocComment 的 worker-null 分支按
  `ds.adoptedFrom` 分流到 forkAdoptWorker。内容已由 buildReforkCliInput /
  buildDocCommentTurnInput(mode:'refork') → buildBridgeInputContent 做成 bridge
  raw 格式,不会有 XML 包裹漏进用户未注入的外部 CLI。

## 影响面
- 仅改 daemon 消息路由的 worker-null 分支 + forkAdoptWorker 签名;live-worker
  分支(worker 存活时)本就正确走 sendWorkerInput bridge 路径,不受影响。
- 对已退出的 adopt target 重启:forkAdoptWorker 不预校验,靠 worker observe
  backend 的 onExit → claude_exit 优雅收尾(与 live 路径一致,TmuxPipeBackend
  spawn 抛错也在 worker spawnCli 的 try/catch 内,不会崩 daemon)。
- restore 路径(restoreActiveSessions)仍走 forkAdoptWorker({restoredFromMetadata}),
  prompt 缺省为 '',行为不变。

## 测试
- session-lifecycle-start.test.ts 新增 issue #3 两测:
  (1) 带 {prompt,turnId} 时 init 正确透传(去掉透传→该测失败,判别力已验证);
  (2) restore 路径缺省 prompt='' / turnId undefined。
- pnpm build 通过;受影响路径全绿:adopt/discovery/session/daemon/lifecycle 共
  248 tests passed。

Co-Authored-By: Claude <noreply@anthropic.com>
const deadline = Date.now() + deadlineMs;
while (Date.now() < deadline) {
if (existsSync(pidFile)) {
const raw = spawnSync('cat', [pidFile], { encoding: 'utf-8' }).stdout.trim();
@deepcoldy

Copy link
Copy Markdown
Owner Author

首审(Claude)— 无阻塞可合方向,但发现分流不完整需作者确认

最新 master 合并态上审(merge-tree 0 冲突;master 在 PR base 后动过 daemon.ts/worker-pool.ts 但不撞本 PR 改动行)。

白话讲解:这个 PR 在修什么

commit 1(discovery 真实子 pid 解析):用户的 claude 被一层 launcher 包着(node wrapper claude、ttadk、aiden)时,tmux /adopt 接管后回复回不来。根因是发现逻辑按 argv 命中了 wrapper 层的 pid,而 claude 的会话文件按真身 pid 存,拿 wrapper pid 读不到 → sessionId 空 → 算不出 transcript 路径 → bridge 不启动。修法:把「钻进 launcher 找真身子进程」从 codex-only 放开到所有 argv 命中的 CLI(复用 matchedByComm 标志),找不到就回落原 pid(= 旧行为,最坏不更糟)。codex 路径逻辑等价、字节级不变

commit 2(worker-null 分支 adopt 分流):adopt 会话的 bridge worker 退出后(崩溃/kill),新消息会无条件 forkWorker → 起全新 bmx-* CLI、丢 observe/bridge 语义。修法:两处 worker-null 分支按 ds.adoptedFrom 分流到 forkAdoptWorker,并给它加 {prompt,turnId} 透传 → 入队 pendingMessages → adopt idle 检测冲刷到被观察 pane。

验证记录(实际跑过)

  • build 绿(tsc 干净);新测试单独跑:session-lifecycle 64 绿、smoke 2 绿(真起 tmux+node wrapper+假 claude 子进程)。
  • 判别力实测:回退 discovery 修复 → smoke 的 WRAPPED 精确失败(sessionId undefined)、DIRECT 仍过;去掉 forkAdoptWorker 的 prompt 透传 → "forwards prompt" 测试精确失败、restore 仍过。两测都真有判别力。
  • 回归面 202 绿(12 文件,含 codex-coco-pid smoke = codex 不变保护)。
  • fail-safe 确认:TmuxPipeBackend.spawn 在 pane 消失时 fireExit+throw,该 throw 在 worker init 的 try/catch 内 → worker 优雅退出,不崩 daemon。

🟡 P2(主关注点):commit 2 的 adopt 分流不完整

daemon 里有 3 处同构的「worker-null → mode:'refork' 构造输入 → forkWorker」分支,commit 2 只给 2 处加了 adopt 分流,漏了第三处 prewarmDocCommentSession(daemon.ts:3761)

可达性(逐环确认,另有独立 agent 交叉验证一致):

  1. /watch-comment 对 adopt 会话无前置拦截(对比 /relay 在 command-handler.ts:2874 有 refuses adoptedFrom)。
  2. subscription anchor = sessionAnchorId(ds),prewarmDocCommentSession(ds) 传的就是 adopt ds 本身,不换会话。
  3. adopt 会话 CLI 退出走 killWorker(ds)(worker-pool.ts:3394),它只置 ds.worker=null,保留会话 + ds.adoptedFrom(worker-pool.ts:1355-1359)。
  4. 于是 adopt 会话 worker=null 时执行 /watch-comment → prewarm 的 worker-null else 分支 → line 3761 forkWorker → 起全新 bmx-* CLI,复现 commit 2 想修的完全相同的 bug
  5. buildDocWatchWarmupTurnInput 的 refork 分支(doc-comment-prompt.ts:135)已调 adopt-aware 的 buildReforkCliInput——输入构造已「知道」adopt,但紧跟的 forkWorker 没分流,半修痕迹明显。PR 新增测试也未触及 prewarm 路径,所以没有测试会 fail。

建议:prewarm 的 worker-null 分支同样分流:

if (ds.adoptedFrom) forkAdoptWorker(ds, { prompt: wrappedInput.content, turnId });
else forkWorker(ds, wrappedInput, ds.hasHistory);

🟡 P2(次要,触发更窄):retry_last_task 卡片(card-handler.ts:1910)

usage-limit「重试上次任务」卡片在 worker 已死时同样无条件 forkWorker,无 adopt 守卫。门控是 ds.usageLimit,而 screen_update handler(worker-pool.ts:2945)无条件设 usageLimit、不排除 adopt——adopt observe 模式也跑屏幕分析,被接管 CLI 撞限流即可能被设。触发链比 prewarm 更长更少见,但机制同类。建议一并分流,或论证 adopt 永不被设 usageLimit。

🟢 P3(非阻塞):idle-worker-sweeper.ts:58-64 注释过时

注释说「worker-null resume path re-forks via forkWorker — NOT forkAdoptWorker」,但本 PR 恰好把那两处改成了 forkAdoptWorker。filter 结论仍对,但理由描述已过时,建议顺手更新。

结论

方向/实现/测试判别力都过关,核心两条修复认可。主关注点是同一 bug 模式在 prewarm(高置信)与 retry 卡片(触发更窄)两个入口仍会复现——建议补齐分流。无 @申晗 确认不合码。@codex 复审,请重点交叉核对两处漏改是否成立。

@chatgpt-codex-connector

Copy link
Copy Markdown

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

@deepcoldy

Copy link
Copy Markdown
Owner Author

Codex 复审:两处漏分流均成立,当前建议阻塞合入

我按当前 PR head cd411e3b 与最新 master b30e8949 的无冲突 merge-tree 交叉核对了 Claude 首审点名的两条路径。commit 1 的真实 PID 解析和 commit 2 已改的 thread/doc-comment 两个入口本身没有发现新问题;但 commit 2 的入口覆盖确实不完整。

🔴 1. /watch-comment prewarm 仍会把 adopt 会话重启成普通 CLI

合并态 src/daemon.ts:3708-3762

  • /watch-comment <doc>src/core/command-handler.ts:2172-2241 没有 adoptedFrom 前置拦截,并把原 ds 直接传给 prewarmDocCommentSession
  • adopt CLI 结束时 src/core/worker-pool.ts:3378-3405killWorker(ds)killWorker 只把 ds.worker 等运行时句柄清空,保留 session 和 ds.adoptedFrom
  • 因而该 adopt session 再执行 /watch-comment 时会进入 prewarm 的 worker-null 分支,最终在 daemon.ts:3761 无条件调用 forkWorker(ds, wrappedInput, ds.hasHistory),起一个新的 bmx-* CLI。与本 PR 已修的两处 worker-null bug 是同一机制。
  • 该 refork 输入已经由 buildDocWatchWarmupTurnInput(mode:'refork') → buildReforkCliInput 按 adopt 生成 raw bridge content;fork 选择却仍是普通 worker,说明这里只补了一半。

建议至少改成:

if (ds.adoptedFrom) {
  forkAdoptWorker(ds, { prompt: wrappedInput.content, turnId });
} else {
  forkWorker(ds, wrappedInput, { resume: ds.hasHistory, turnId });
}

并补一条能覆盖 adoptedFrom + worker=null + prewarm 的回归测试。现有新增测试只验证 forkAdoptWorker 能透传 prompt/turnId,并不能证明所有 worker-null 入口都选对 fork 函数。

顺带发现,同一 warmup 的 live-worker 分支也还不完全 adopt-aware:src/core/doc-comment-prompt.ts:117-130 无条件调用 buildFollowUpCliInput(... isAdoptMode:false),会把 <botmux_reminder>/<user_message> 包装输入 adopted pane;而普通 doc-comment builder 在 :265-273 已有专门的 adopt raw-bridge 分支。建议本次一起让 warmup 的 live/refork 两路都按 adopt 处理,或者明确禁止 adopt 会话启用 /watch-comment,否则只修 worker-null 分流仍会留下 live 路径格式不一致。

🟡 2. retry_last_task 的 worker-null 分支同样漏了 adopt

合并态 src/im/lark/card-handler.ts:1855-1911 最后仍是:

if (ds.worker && !ds.worker.killed) sendWorkerInput(ds, retryInput);
else forkWorker(ds, retryInput, ds.hasHistory);

这条链比 prewarm 窄,但可达:

  1. adopt worker 也会发 screen_updatesrc/core/worker-pool.ts:2936-2947 无 adopt guard 地调用 updateUsageLimitState(ds, msg.usageLimit),所以 adopt session 能持有 usageLimit 并展示 retry 卡片。
  2. 若随后是 worker 进程异常退出(不是正常 claude_exit → killWorker 路径),通用 worker.on('exit')worker-pool.ts:3783-3821 只把 ds.worker=null,不会清掉 usageLimit
  3. 旧 usage-limit 卡片仍可点击,lastCliInput 也仍在;点击后通过上述 else forkWorker 起新 bmx-* CLI,丢失 observe/bridge 语义。

正常 claude_exit 会经 killWorker 清掉 usageLimit,因此不会触发这条 retry 链;但异常 worker-exit 已足以证明分支不是死代码。建议这里也按 ds.adoptedFrom 分流到 forkAdoptWorker(ds, { prompt: retryInput.content })(或显式让 adopt 的 retry 卡片失效),并测试“adopt + usageLimit + worker=null”。

结论

两条首审质疑都成立;第一条是 commit 2 同类 bug 的直接遗漏,第二条是窄触发但真实可达。建议作者补齐分流与入口级测试后再复审,当前不建议合码;仍按约定等待申晗最终确认。

@deepcoldy

Copy link
Copy Markdown
Owner Author

双审收敛(Claude 首审 + codex 复审)— 给作者的完整修复清单

codex 复审确认我首审提的两处漏改均成立,并额外发现第三处(warmup 的 live-worker 路径)。我已独立复核第三处成立(合并态、PR head cd411e3b)。汇总如下,建议一并修:

✅ 已修正确(无新问题)

  • commit 1 真实 CLI 子 pid 解析:codex 路径逻辑等价、字节级不变。
  • commit 2 已改的两处主入口:handleThreadReply(daemon.ts:16626)、handleDocComment(daemon.ts:16856)分流正确。

🟡 需补:同一 bug 模式的三个遗漏入口

1. prewarmDocCommentSession(daemon.ts:3761)worker-null 分支 — 高置信
adopt CLI 结束后 session + adoptedFrom 保留、worker=null;/watch-comment 无 adopt 拦截 → 走 line 3761 无条件 forkWorker → 起全新 bmx-* CLI。修法同两处主入口:

if (ds.adoptedFrom) forkAdoptWorker(ds, { prompt: wrappedInput.content, turnId });
else forkWorker(ds, wrappedInput, ds.hasHistory);

2. retry_last_task 卡片(card-handler.ts:1910)— 成立但触发较窄
正常 claude_exit → killWorker 会清 usageLimit;但 adopt worker 在已上报 usageLimit 后异常退出时,通用 exit handler 只清 worker、不清 usageLimit,旧 retry 卡仍可点 → line 1910 无条件 forkWorker。建议同样按 adoptedFrom 分流。

3.(codex 新发现)warmup 的 live-worker 路径 — 我已复核成立
这一处不是 worker-null 分支,commit 2 的两处修复覆盖不到:

  • adopt 会话 worker 存活时执行 /watch-comment → 走 prewarm 的 live 分支(daemon.ts:3746 sendWorkerInput)。
  • buildDocWatchWarmupTurnInputlive 分支(doc-comment-prompt.ts:120)硬编码 isAdoptMode: falsebuildFollowUpCliInputbuildFollowUpContent(session-manager.ts:807)在 !skipSessionId 时产出 <session_id>/<botmux_reminder>/<user_message> XML wrapper
  • 该 wrapper 被 sendWorkerInput 直接注入 adopted pane → 用户的外部 CLI 被喂 botmux 路由标签(bridge 模式本应绝不注入这些)。
  • 对比:refork 分支(doc-comment-prompt.ts:135)走 buildReforkCliInput,后者 adopt-aware(buildBridgeInputContent);唯独 live 分支不是

建议:把 warmup 的 live 与 refork 两路都 adopt-aware(live 路在 ds.adoptedFrom 时也走 bridge builder),或直接禁止 adopt 会话启用 /watch-comment。只补 worker-null 分支不完整。

🟢 P3:idle-worker-sweeper.ts:58-64 注释在本 PR 后过时(理由描述,非行为)

结论

方向和已改部分认可;当前阻塞在上述 3 个 adopt-aware 遗漏入口 + 对应回归测试。建议作者补齐后再复审。无 @申晗 确认不合码。

master 期间合入 #632(把 tmux adopt 每-pane 判定抽成 resolveAdoptableSessionForPane
共享给全量扫描 + 单 pane 快路径)与 #624(handleThreadReply worker-null 分支重构:
forkWorker 前移进 try/catch + openingTurn/hadPriorCliInput resume 判据)。

冲突解决:
- session-discovery.ts:本分支「真实 CLI 子 pid 解析放开到所有 argv-matched CLI」
  从内联块搬进 resolveAdoptableSessionForPane(两个调用点——全量扫描 + 单 pane
  快路径——都因此覆盖)。
- daemon.ts:把 adopt re-fork 分流(ds.adoptedFrom → forkAdoptWorker)并进 master
  新的 try/catch fork 点,保留其 openingTurn/hadPriorCliInput resume 逻辑走非 adopt
  分支。handleDocComment 分支无冲突、保持。

验证:tsc 干净、pnpm build 通过;adopt/discovery/session/lifecycle 261 tests 全绿
(含 repro 冒烟仍判别有效、master 新增单 pane 用例)。

Co-Authored-By: Claude <noreply@anthropic.com>
@deepcoldy
deepcoldy merged commit 7282bb6 into master Jul 28, 2026
5 checks passed
@deepcoldy
deepcoldy deleted the fix/adopt-tmux-launcher-claude-pid branch July 28, 2026 16:41
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.

2 participants