part 6 修复:worktree 根移出 /app——RETEST5 挂载点回归

RETEST5:part 5 的 --bind=<workspace>/.chv-worktrees:/app/work
(adc7bf6,排在 --bind-ro=/app 之后)让所有 cell 启动失败:
后挂的 bind 只有在挂载点已存在时才能盖住,挂载点不存在时
nspawn 在只读 /app 下连建都建不起来(Failed to create mount
point .../app/work: Read-only file system,全量 73 次,
CELL-RC=0 共 0 次),缺陷 1 的 15/15 回退为 0/15。

三选一取 (a) worktree 根移出 /app:
- chv-cell 不再发任何指向 /app 之下的 bind,删
  --bind=...:/app/work 与 worktrees_dir 参数;WORKTREE_DIR
  (/home/chv/workspace/.chv-worktrees)改经
  --setenv=CHV_WORKTREE_ROOT 传进单元——该路径在已可写绑进
  单元的 workspace 之下,挂载点问题按构造即不存在;backing
  仍在客机 workspace 下随磁盘持久化,隔离与落盘语义不变,
  只是单元内路径从 /app/work/host-worktrees 改为
  /home/chv/workspace/.chv-worktrees/host-worktrees。
- host.py 新增 worktree_base():CHV_WORKTREE_ROOT 优先;
  CHV_CELL=1 但变量未设时回落到同一单元内路径(旧版 chv-cell
  / 单元内手动跑 host.py 仍不出 /app);单元外沿用
  HERE/work/host-worktrees,行为不变。make_worktree 与
  dispatch_from_branch(第二个 HERE/work 调用方)同走该函数,
  goal/fork/依赖链一并修到。
- 不选 (b) 默认 --no-worktree:隔离丢了,且 goal/fork 同样
  调 make_worktree,照样撞 Errno 30;不选 (c) 镜像预建空
  目录 /app/work:修法耦合镜像构建,某次重建漏建即静默回归,
  且单测无法钉住镜像内容。
- 根因 1(39084ce)与根因 3(0203f32)代码未动。

单测:删 part 5 钉 argv 顺序的 4 项(该顺序本身已被真机证伪),
新增 CellPart6WorktreeTest 4 项 + HostWorktreeBaseTest 5 项:
argv 里无任何指向 /app 之下的 bind、CHV_WORKTREE_ROOT 经
setenv 进单元、worktree 根只经可写 workspace 绑定可达、
两处同值常量一致、worktree_base 三路选择、make_worktree
真 git worktree 落点、dispatch_from_branch 同根。

全量:cloud/ 下 discover -s tests,2067 项 OK(skipped=9)。
(注:仓库根跑 discover 会把 queue 解析成标准库、致
test_queue_sandbox 导入失败,须在 cloud/ 下跑。)
This commit is contained in:
host committed 2026-11-07 14:23:48 +00:00
1 parent 0203f32627
commit 50cae2a0af
3 files changed
+249 -55

No files matched your search

+38 -2
View File
@@ -61,6 +61,40 @@ from concurrent.futures import ThreadPoolExecutor
HERE = os.path.dirname(os.path.abspath(__file__))
DEFAULT_STATE = os.path.join(HERE, "host-state.json")
# part 6(RETEST5 新发现):单元内 worktree 根,绝不在 /app 之下。
# 单元内 HERE=/app,而 /app 是 chv-cell 的 --bind-ro;part 5 曾
# 把客机 workspace 下的 .chv-worktrees 以可写 bind 挂到
# /app/work 上(argv 里排在 --bind-ro=/app 之后,指望后挂盖住
# 只读子树),RETEST5 真机证明该设想不成立:后挂的 bind 只有在
# 挂载点已存在时才能盖住,挂载点不存在时 nspawn 在只读 /app 下
# 连建都建不起来(`Failed to create mount point .../app/work:
# Read-only file system`,全量 73 次,CELL-RC=0 共 0 次),
# 所有 cell 启动失败。修法三选一取 (a) worktree 根移出 /app:
# 单元内改用 workspace 绑定下已可写的
# /home/chv/workspace/.chv-worktrees(该路径经 --bind 整个
# workspace 以可写方式进单元,无需任何额外 bind,挂载点问题
# 按构造即不存在);(b) 默认 --no-worktree 不取:隔离丢了,
# 且 goal/fork 同样调 make_worktree,照样撞 Errno 30;(c) 镜像
# 里预建空目录 /app/work 不取:修法耦合镜像构建,某次重建漏
# 建即静默回归,且单测无法钉住镜像内容。
# CHV_WORKTREE_ROOT 由 chv-cell 经 --setenv 传进来(与
# chv-cell.py 的 WORKTREE_DIR 同值);CHV_CELL=1 但变量未设
# 时(旧版 chv-cell / 单元内手动跑 host.py)回落到同一单元内
# 路径,单元内 worktree 根永不在 /app 之下;单元外沿用
# HERE/work/host-worktrees,行为不变。
CHV_WORKTREE_ROOT_ENV = "CHV_WORKTREE_ROOT"
CELL_WORKTREE_ROOT = "/home/chv/workspace/.chv-worktrees"
def worktree_base():
"""worktree 根目录(<根>/host-worktrees/<任务>)。"""
root = os.environ.get(CHV_WORKTREE_ROOT_ENV)
if root:
return os.path.join(root, "host-worktrees")
if os.environ.get("CHV_CELL") == "1":
return os.path.join(CELL_WORKTREE_ROOT, "host-worktrees")
return os.path.join(HERE, "work", "host-worktrees")
def _sum_turn_usage(events, thread_id, turn_id):
"""从事件窗口里汇总一回合的上游真实用量。
@@ -1242,7 +1276,7 @@ def git(args, cwd):
def make_worktree(repo, task_name):
"""一任务一 worktree;不是 git 仓库时退化为独立目录拷贝。"""
repo = os.path.abspath(repo)
base = os.path.join(HERE, "work", "host-worktrees")
base = worktree_base()
os.makedirs(base, exist_ok=True)
path = os.path.join(base, task_name)
if os.path.exists(path):
@@ -1774,7 +1808,9 @@ def dispatch_from_branch(server, state, name, prompt, repo, dep_branch,
c = git(["commit", "-m", f"host: {name} base from {dep_branch}"],
dep_worktree)
# nothing to commit 时分支里已有全部改动,继续用它当基底
base = os.path.join(HERE, "work", "host-worktrees")
# part 6:与 make_worktree 同款,worktree 根经 worktree_base()
# 选(单元内在 workspace 下,不在只读 /app 之下)。
base = worktree_base()
os.makedirs(base, exist_ok=True)
path = os.path.join(base, name)
if os.path.exists(path):