Skip to content

fix(sandbox): 只读暴露 traex/coco 迁移标记,修 goal-mode 启动卡首运行迁移弹窗 - #714

Merged
deepcoldy merged 3 commits into
masterfrom
fix/traex-coco-sandbox-migration-wedge
Aug 3, 2026
Merged

fix(sandbox): 只读暴露 traex/coco 迁移标记,修 goal-mode 启动卡首运行迁移弹窗#714
deepcoldy merged 3 commits into
masterfrom
fix/traex-coco-sandbox-migration-wedge

Conversation

@deepcoldy

@deepcoldy deepcoldy commented Aug 3, 2026

Copy link
Copy Markdown
Owner

症状

沙盒(sandbox:true)的 traex / coco bot 在 goal-mode 冷启动后卡死在交互弹窗:

Legacy TRAE CLI data detected
Would you like to migrate detected data to the new TRAE CLI storage?
  Migrate recent sessions from /root/.cache/coco/sessions to /root/.trae…

goal-mode pane 背后没有真人在 PTY,弹窗永远等不到答复 → 会话 wedge,bot 跑不起来。(申晗 2026-08-03 拿 Traex 实测撞到。)

根因(子代理真机 node-pty 复现确认)

traecli 判断迁移是否完成读 ~/.trae 根目录下的 done-marker。沙盒 authPaths 只 bind ~/.trae/cli 子目录 → 迁移 SOURCE ~/.cache/coco(baseline rw bind)可见,但 done-marker(根目录)不可见 → traecli 判"待迁移" → 弹窗。coco 与 traex 是同一 traecli 二进制(软链),同坑。

精确事实(纠正初版 PR 的误判):

  • 门控 session 迁移弹窗(本次 wedge)的是 .coco-rollouts-migrated不是 .coco-migrated(后者门控 config 迁移)
  • traecli 只看 marker 是否存在(0 字节 + chmod 444 即可),内容/可写性无关
  • DISABLE_ACTIVE_MIGRATION 是 QUIC/OpenSSL 传输参数(RFC 9000),与迁移无关;env / -c disable_active_migration 均无效

改法(v2:codex 复审后收窄)

初版把 authPaths 扩到整个 ~/.trae —— codex 正确指出 authPaths 编译成 readWritefs-policy.ts:533),会把 ~/.trae 根下 hooks//plugins/(command hook,后续 CoCo 会话都加载)/skills//traecli.toml 全部 RW 暴露给受聊天输入驱动的沙盒 = 跨 bot / 出沙盒完整性边界扩大;且 codex.ts 的 ~/.codex 整目录 bind 类比不成立(codex 有 supportsReadIsolation,沙盒下宿主根被丢弃;traex/coco 没有)。

收窄为只读暴露 marker,不碰 authPaths 的 RW 面

  • 新增 adapter 可选方法 sandboxReadonlyPaths()types.ts,镜像 sandboxExtraExecPaths),worker 接进 fs-policy 的 readonlyRoots 通道(→ readOnly 规则 + 存在性过滤)
  • traex/coco 的 authPaths 还原~/.trae/cli(+ coco 的 ~/.cache/coco),改由 sandboxReadonlyPaths() 只读暴露两个 marker:.coco-rollouts-migrated(本次)+ .coco-migrated(纵深)
  • 策略级验证:marker→readOnly、cli/→readWrite、hooks//plugins//traecli.toml→none(不可写

P1b:marker version bump(存量 pane 冷启动迁移)

sandboxReadonlyPaths 是新的 spawn-time bwrap mount;warm reattach 保留旧进程+旧 mount,存量 v3.8 pane 会 reattach 且仍缺 marker mount、继续卡。bump ISOLATION_PANE_MARKER_VERSION → 强制冷启动。#709 排序#709(→8 env 契约) 与 #714(→9 mount 契约) 独立;本 PR 取 9 假设 #709 先合占 8,顺序反了则后合者 rebase 保持单调(注释已写明)。

验证

Co-Authored-By: Claude Opus 5 noreply@anthropic.com

deepcoldy added a commit that referenced this pull request Aug 3, 2026
…cker)

codex 复审 PR #714 抓的两个 blocker,都独立复核成立,据此重做。

## P1a:`~/.trae` 整目录 authPaths = readWrite 暴露面(原方案过宽)
`fs-policy.ts:533` `push(ctx.authPaths, 'readWrite', 'adapter')` —— authPaths 一律
编译成 **readWrite**。`~/.trae` 根下有 hooks/ plugins/(带 command hook,后续所有
CoCo 会话都加载)skills/ traecli.toml(信任哈希)。整目录 RW 给受聊天输入驱动的
沙盒 = 让它能改被别的 bot(甚至用户宿主上非沙盒 traecli)执行的 hook/plugin 代码,
是确定的跨 bot / 出沙盒完整性边界扩大。且 codex.ts 的 `~/.codex` 类比不成立:codex
有 `supportsReadIsolation: true`,沙盒下宿主 `~/.codex` 被 authPathsSurvivingCli
DataRedirect 主动丢弃;traex/coco 没有这层重定向。

### 改法:只读暴露 marker,不碰 authPaths 的 RW 面
- 新增 adapter 可选方法 `sandboxReadonlyPaths()`(types.ts,镜像 sandboxExtra
  ExecPaths),worker 把它接进 fs-policy 的 `readonlyRoots` 通道(→ readOnly 规则,
  经 keepExisting 存在性过滤)。
- traex/coco 的 `authPaths` **还原**成原来的 `~/.trae/cli`(+ coco 的 ~/.cache/coco),
  改为 `sandboxReadonlyPaths()` 返回两个 done-marker(只读):
    · `~/.trae/.coco-rollouts-migrated` —— 门控「迁移 recent SESSIONS」弹窗(本次 wedge)
    · `~/.trae/.coco-migrated`          —— 门控 config 迁移弹窗(纵深)
- 关键事实(子代理真机 node-pty 复现确认,纠正了原 PR 的两处误判):
    · 门控 session 弹窗的是 `.coco-rollouts-migrated`,**不是** `.coco-migrated`
    · traecli 只看 marker **是否存在**(0 字节 + chmod 444 即可),内容/可写性无关
      → 只读单文件 bind 足够,无需任何可写面
    · `DISABLE_ACTIVE_MIGRATION` 是 QUIC/OpenSSL 传输参数(RFC 9000),与迁移无关;
      env / `-c disable_active_migration` 均无效(原设想的 env skip 走不通)

策略级验证(buildFsPolicy + accessForPath):marker→readOnly、cli/→readWrite、
hooks//plugins//traecli.toml→none(deny-by-default,**不可写**)。

## P1b:marker version 没 bump,存量 pane 部署后不迁移
`sandboxReadonlyPaths` 是新的 spawn-time bwrap mount;warm reattach 保留旧进程 +
旧 mount 集,v3.8 存量 pane 会 reattach 且仍缺 marker mount、继续卡弹窗。bump
`ISOLATION_PANE_MARKER_VERSION` → 强制冷启动迁移。

### 与 #709 的版本排序
#709(→8,env 契约)与 #714(→9,mount 契约)是两个独立启动契约。本 PR 取 9,
假设 #709 先合占 8;若合并顺序反了,后合者 rebase 保持严格单调(否则夹在两次部署
之间的中间版本 pane 会绕过第二次迁移)。已在常量注释写明。

## 测试
- 全部 build 绿;cli-adapters 313 + fs-policy 66(+1) + read-isolation(+#714块) +
  sandbox 16 + child-env = 444 全绿
- 变异验证有牙:marker 9→8 → #714 reattach 块 2 条红;authPaths 改回 ~/.trae →
  fs-policy「sibling 不可写」断言红
- fs-policy 新增断言钉到最终 policy:marker 只读 / cli/ 可写 / hooks·plugins·
  traecli.toml 不可写

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@deepcoldy deepcoldy changed the title fix(sandbox): traex/coco authPaths 扩到整个 ~/.trae,修沙盒 goal-mode 卡首运行迁移弹窗 fix(sandbox): 只读暴露 traex/coco 迁移标记,修 goal-mode 启动卡首运行迁移弹窗 Aug 3, 2026

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复审结论:🟢 代码通过(review head:b5c94fbee);当前 GitHub 身份与 PR 作者相同,平台不允许 self-approve,因此以 review comment 记录结论,未执行合并。

关键复核:

  • supportsReadIsolation 目前只由 codex / claude-code 声明;traex/coco 不走 BOT_HOME 数据重定向。新 sandboxReadonlyPaths() 独立进入 expandTildeLexical → keepExisting → readonlyRoots,因此两个真实 marker 会生效,且不会把 ~/.trae 根重新作为 RW authPath 暴露。
  • no-transport 的 dropAuthority 只覆盖 botmux / lark-cli authority roots;~/.trae/.coco-* 不在其中,不会被误丢。
  • marker 最终是 fs-policy readOnly;Linux bwrap 编译为 --ro-bind,macOS Seatbelt 为 read allow + write deny。~/.trae/cli 仍 RW,hooks/plugins/skills/traecli.toml 仍 none
  • PTY 与各持久后端的新进程共用该 spawn/policy 路径;v9 marker 会拒绝 v7/v8 pane,确保旧 mount 的 warm reattach 被冷启动替换。

实际验证:pnpm build 通过;cli-adapters + fs-policy + read-isolation + sandbox + child-env444/444 通过;当前 GitHub build/CodeQL 全绿。

合并期注意(非代码 blocker):

  1. 优先让 #709 先落(marker 8),本 PR 再 rebase/落 9;若顺序反过来,后落的 #709 必须再 bump 到 10,不能保留 8/9,否则已有中间版本 pane 会绕过第二次迁移。
  2. marker 版本是全局的,所以该 bump 会让所有旧版 isolated persistent pane 一次性 cold-respawn,不只 traex/coco;这是当前全局 marker 机制的预期迁移代价,发布/影响面说明里应明确。

按话题约束,仍须申晗明确确认后才可合码。

deepcoldy added a commit that referenced this pull request Aug 3, 2026
…lta 3 P3)

codex delta 复审的 3 条 P3(非 blocker,代码面已通过):

1. PR 标题(已在 GitHub 改):旧标题写"authPaths 扩到整个 ~/.trae",与最终代码
   (只读暴露 marker、authPaths 还原 cli/)正相反,会误导后续安全审计。

2. worker wiring 守卫(本 commit):原本 adapter 测试只断言 sandboxReadonlyPaths()
   的返回值,fs-policy 测试手写 readonlyRoots —— 若有人误删 worker 里那行把方法输出
   spread 进 readonlyRoots 的接线,两组测试都会继续绿,而沙盒里 marker 静默消失、
   goal-mode traex 又会卡迁移弹窗。补一条 source 守卫断言 worker.ts 确实 thread 了
   `...[...(cliAdapter.sandboxReadonlyPaths?.() ?? [])].map(expandTildeLexical)`。
   变异验证:删掉该 spread → 守卫变红。

3. traex.ts marker 注释收窄:原写"a host without them is fine"过宽。改为准确表述——
   marker 缺失只是被 keepExisting 存在性过滤丢弃、不会导致 bind 失败;但这不等于
   "goal-mode 没问题":若迁移 SOURCE(~/.cache/coco)存在而 marker 真缺失,弹窗会
   合理出现。正常机队状态是 marker 已 host 侧写好,本 bind 只是让沙盒看见这份宿主事实。

测试:build 绿;cli-adapters + fs-policy + read-isolation + sandbox + child-env
445 全绿(含新增 wiring 守卫)。

注:合并顺序门(#709 先合占 8 → #714 rebase 保持 9)不在本 commit —— 那是 Git 图
落地顺序,待 #709 合入后 rebase 处理。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
deepcoldy and others added 3 commits August 3, 2026 05:16
## 症状
沙盒(sandbox:true)的 traex / coco bot 在 goal-mode 里冷启动后卡死在交互弹窗:

    Legacy TRAE CLI data detected
    Would you like to migrate detected data to the new TRAE CLI storage?
    Migrate recent sessions from /root/.cache/coco/sessions to /root/.trae…

goal-mode pane 背后没有真人在 PTY,这个弹窗永远等不到答复 → 整个会话 wedge,
bot 一句话都跑不出来。

## 根因(真机 bwrap cmdline 实证)
traecli 判断"coco 迁移是否已完成"读的是 ~/.trae **根目录**下的 done-marker
(`.coco-migrated` / `.coco-migration-skip-all`)——二进制里字面写着
"Legacy config migration already completed (found .coco-migrated)"。

但适配器 authPaths 只 bind 了 `~/.trae/cli` 这一个子目录:
  · fresh-tmpfs 沙盒里,不在 authPaths 的路径 = 不存在
  · 所以沙盒里看不到 ~/.trae/.coco-migrated(done-marker 在根,没被 bind)
  · 而迁移 SOURCE `~/.cache/coco` 恰恰是 bind 进来的(transcript bridge 要读)
  · source 可见 + done-marker 不可见 → traecli 判定"迁移待处理" → 弹窗 → 卡死

coco 与 traex 是**同一个 traecli 二进制**(coco→traex 软链),authPaths 同样只
bind cli/,所以两个适配器同一个坑,一并修。

## 改法
authPaths `~/.trae/cli` → `~/.trae`(整个目录),对齐 codex.ts 早就在用的
`~/.codex` 整目录 bind。这样沙盒里 traecli 读得到根目录的 done-marker,跳过迁移。
coco 保留额外的 `~/.cache/coco`(bridge 读 events.jsonl 用)。

无新增泄漏面:
  · traecli.toml 里没有 API key(只有 model/hooks/marketplaces/projects;
    model_provider 的凭证在 ~/.trae 之外),扩 bind 不暴露密钥
  · ~/.trae 本就是全局共享(没有 per-bot TRAE_HOME),两个 traex bot 早就
    共用同一份,整目录 bind 不新增任何跨 bot 边界
  · SQLite 锁语义不变(cli/ 仍在 bind 范围内)

## 验证
- pnpm build 绿
- cli-adapters(313) + sandbox(16) + fs-policy(66) 等 404 测试全绿
- 更新两条 authPaths 断言并写清迁移 wedge 的 root cause(narrow 回 cli/ 即重现)
- 真机 bwrap cmdline 确认了 `--bind /root/.cache`(source 可见) 与
  `--bind /root/.trae/cli`(done-marker 不可见) 的两侧错配

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…cker)

codex 复审 PR #714 抓的两个 blocker,都独立复核成立,据此重做。

`fs-policy.ts:533` `push(ctx.authPaths, 'readWrite', 'adapter')` —— authPaths 一律
编译成 **readWrite**。`~/.trae` 根下有 hooks/ plugins/(带 command hook,后续所有
CoCo 会话都加载)skills/ traecli.toml(信任哈希)。整目录 RW 给受聊天输入驱动的
沙盒 = 让它能改被别的 bot(甚至用户宿主上非沙盒 traecli)执行的 hook/plugin 代码,
是确定的跨 bot / 出沙盒完整性边界扩大。且 codex.ts 的 `~/.codex` 类比不成立:codex
有 `supportsReadIsolation: true`,沙盒下宿主 `~/.codex` 被 authPathsSurvivingCli
DataRedirect 主动丢弃;traex/coco 没有这层重定向。

- 新增 adapter 可选方法 `sandboxReadonlyPaths()`(types.ts,镜像 sandboxExtra
  ExecPaths),worker 把它接进 fs-policy 的 `readonlyRoots` 通道(→ readOnly 规则,
  经 keepExisting 存在性过滤)。
- traex/coco 的 `authPaths` **还原**成原来的 `~/.trae/cli`(+ coco 的 ~/.cache/coco),
  改为 `sandboxReadonlyPaths()` 返回两个 done-marker(只读):
    · `~/.trae/.coco-rollouts-migrated` —— 门控「迁移 recent SESSIONS」弹窗(本次 wedge)
    · `~/.trae/.coco-migrated`          —— 门控 config 迁移弹窗(纵深)
- 关键事实(子代理真机 node-pty 复现确认,纠正了原 PR 的两处误判):
    · 门控 session 弹窗的是 `.coco-rollouts-migrated`,**不是** `.coco-migrated`
    · traecli 只看 marker **是否存在**(0 字节 + chmod 444 即可),内容/可写性无关
      → 只读单文件 bind 足够,无需任何可写面
    · `DISABLE_ACTIVE_MIGRATION` 是 QUIC/OpenSSL 传输参数(RFC 9000),与迁移无关;
      env / `-c disable_active_migration` 均无效(原设想的 env skip 走不通)

策略级验证(buildFsPolicy + accessForPath):marker→readOnly、cli/→readWrite、
hooks//plugins//traecli.toml→none(deny-by-default,**不可写**)。

`sandboxReadonlyPaths` 是新的 spawn-time bwrap mount;warm reattach 保留旧进程 +
旧 mount 集,v3.8 存量 pane 会 reattach 且仍缺 marker mount、继续卡弹窗。bump
`ISOLATION_PANE_MARKER_VERSION` → 强制冷启动迁移。

假设 #709 先合占 8;若合并顺序反了,后合者 rebase 保持严格单调(否则夹在两次部署
之间的中间版本 pane 会绕过第二次迁移)。已在常量注释写明。

- 全部 build 绿;cli-adapters 313 + fs-policy 66(+1) + read-isolation(+#714块) +
  sandbox 16 + child-env = 444 全绿
- 变异验证有牙:marker 9→8 → #714 reattach 块 2 条红;authPaths 改回 ~/.trae →
  fs-policy「sibling 不可写」断言红
- fs-policy 新增断言钉到最终 policy:marker 只读 / cli/ 可写 / hooks·plugins·
  traecli.toml 不可写

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lta 3 P3)

codex delta 复审的 3 条 P3(非 blocker,代码面已通过):

1. PR 标题(已在 GitHub 改):旧标题写"authPaths 扩到整个 ~/.trae",与最终代码
   (只读暴露 marker、authPaths 还原 cli/)正相反,会误导后续安全审计。

2. worker wiring 守卫(本 commit):原本 adapter 测试只断言 sandboxReadonlyPaths()
   的返回值,fs-policy 测试手写 readonlyRoots —— 若有人误删 worker 里那行把方法输出
   spread 进 readonlyRoots 的接线,两组测试都会继续绿,而沙盒里 marker 静默消失、
   goal-mode traex 又会卡迁移弹窗。补一条 source 守卫断言 worker.ts 确实 thread 了
   `...[...(cliAdapter.sandboxReadonlyPaths?.() ?? [])].map(expandTildeLexical)`。
   变异验证:删掉该 spread → 守卫变红。

3. traex.ts marker 注释收窄:原写"a host without them is fine"过宽。改为准确表述——
   marker 缺失只是被 keepExisting 存在性过滤丢弃、不会导致 bind 失败;但这不等于
   "goal-mode 没问题":若迁移 SOURCE(~/.cache/coco)存在而 marker 真缺失,弹窗会
   合理出现。正常机队状态是 marker 已 host 侧写好,本 bind 只是让沙盒看见这份宿主事实。

测试:build 绿;cli-adapters + fs-policy + read-isolation + sandbox + child-env
445 全绿(含新增 wiring 守卫)。

注:合并顺序门(#709 先合占 8 → #714 rebase 保持 9)不在本 commit —— 那是 Git 图
落地顺序,待 #709 合入后 rebase 处理。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@deepcoldy
deepcoldy force-pushed the fix/traex-coco-sandbox-migration-wedge branch from 899ffd5 to e2bbfd9 Compare August 3, 2026 12:26
@deepcoldy
deepcoldy merged commit 3db6452 into master Aug 3, 2026
6 checks 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