Nx 仓库审查沙箱一次性初始化:setup-review-sandbox 完整实操指南
【免费下载链接】nxThe Monorepo Platform that amplifies both developers and AI agents. Nx optimizes your builds, scales your CI, and fixes failed PRs automatically. Ship in half the time.项目地址: https://gitcode.com/GitHub_Trending/nx/nx
在 nrwl/nx(本仓库)中,任何未发布的 PR 代码都被视为不可信代码,绝不能在你的宿主机上执行安装脚本、构建或复现命令。为此仓库内置了一套基于 Docker 的隔离审查沙箱体系,而
setup-review-sandbox技能负责一次性把它的全部前置条件安装到位:Docker 引擎、隔离运行时(Linux 上的 gVisor / macOS 上的 Docker VM)、健康的容器网络,以及内置完整工具链的nx-review-sandbox镜像。读完本文,你将掌握这套沙箱的完整初始化流程、各步骤的故障排查方法、工具链镜像的构建原理,以及磁盘回收与安全边界管理的最佳实践。
一、背景:沙箱在整个审查工作流中的位置
在动手指南之前,先明确这套沙箱服务谁、解决什么问题。仓库把"不可信代码"的边界定义为执行而非阅读:宿主可以自由读取 PR/Issue 的公开信息,但 PR 作者写的代码(postinstall 脚本、构建、测试、复现命令)只能在隔离容器内运行。这个模型由三个组件共同落地:
- reproduce-issue 技能:把某个 issue 的复现过程完全搬进隔离容器内执行,宿主上"零落地";
- reproduce-verifier Agent:PR 审查时验证"这个 PR 是否真的修好了它声称修复的 bug",其 Level 2(PR 构建模式)依赖
nx-review-sandbox镜像; - review-pr 技能:深度 PR 审查的编排者,其 pre-flight 阶段会无条件调用构建脚本。
setup-review-sandbox就是这三者的"地基"。当用户说出"set up the review sandbox"、"install the sandbox prereqs"、"build the sandbox image",或某个 reproduce-issue 预检报告某组件 MISSING 时,就该运行它。
该技能的设计哲学有三点值得先记住:
- 幂等(Idempotent):每一步都先检查、仅在必要时才动手,重复运行只是"验证 + 修复";
- sudo 交接:需要 root 的操作(安装 Docker、注册 runsc、modprobe 等)不会以非交互方式强行 sudo,而是打印给用户、由用户在自己的终端里执行;
- 分平台:Linux 与 macOS 的沙箱边界来源不同,第一步永远是
uname -s——Linux 走 gVisor 路线,macOS(Darwin)走 Docker VM 路线。
二、第一步:Docker 引擎
先确认 Docker 是否可用,一条命令即可:
docker info >/dev/null 2>&1 && echo "docker OK" || echo "docker MISSING"- MISSING 且是 Linux:安装 Docker Engine 后执行
sudo systemctl enable --now docker让服务开机自启,并把当前用户加入docker组(sudo usermod -aG docker $USER,然后重新登录使组生效); - MISSING 且是 macOS:
brew install colima docker后colima start(或直接安装 Docker Desktop)。
注意 macOS 上 Colima 不是可选配菜而是必需组件:它提供的 Linux VM 本身就是后面第 2 节的"沙箱边界"。
三、第二步:隔离运行时
3.1 Linux —— gVisor(runsc)
Linux 上沙箱边界来自 gVisor,它是运行在 Docker 之下的用户态内核。先检查它是否已注册为 Docker 运行时:
docker info --format '{{range $k,$v := .Runtimes}}{{$k}} {{end}}' | grep -q runsc && echo "runsc OK" || echo "runsc MISSING"如果 MISSING,把下面整段交给用户在自己的终端执行(需要 sudo;若用户 shell 是 fish,注意其退出码变量是$status):
sudo apt-get update && sudo apt-get install -y apt-transport-https ca-certificates curl gnupg curl -fsSL https://gvisor.dev/archive.key | sudo gpg --dearmor -o /usr/share/keyrings/gvisor-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gvisor-archive-keyring.gpg] https://storage.googleapis.com/gvisor/releases release main" | sudo tee /etc/apt/sources.list.d/gvisor.list sudo apt-get update && sudo apt-get install -y runsc sudo runsc install # 把 runsc 注册为 Docker 运行时 sudo systemctl restart docker执行完重新跑上面的运行时检查命令确认。
3.2 macOS —— Docker VM 本身就是沙箱
macOS 上没有runsc。因为容器实际跑在 Colima/Docker Desktop 提供的 Linux VM 里,宿主内核与容器内核天然隔离。只需确认 VM 在线:
docker info >/dev/null 2>&1 && echo "docker VM OK" || echo "start it: colima start"3.3 从源码看"为什么必须是 gVisor"
这一设计并非随意选择。沙箱 CLI .claude/tools/sandbox 中定义了三档执行权限none < screened < full,并明确"隔离失败则关闭(fail closed)":在 Linux 上如果runsc未注册,sandbox start会直接拒绝启动,而不是悄悄降级到普通的 runc 容器。其理由是历史教训——"无隔离却报告成功"是每一层之上都看不见的失效模式(一个未设置的运行时变量会展开为空,与 macOS 的正确值在字节上完全相同,肉眼根本无法区分)。这正是本技能第 2 节必须把 gVisor 装到位的原因:它是 Linux 上唯一被认可的隔离边界。
四、第三步:容器网络健康检查(专治veth一类故障)
网络检查是沙箱就绪验证中最容易踩坑的一环,两条命令一起跑:
docker run --rm --network none alpine true && echo "sandbox OK" docker run --rm alpine true && echo "networking OK" || echo "networking BROKEN"- 第一条验证容器本身能否拉起(无网络模式);
- 第二条验证容器网络是否可用。
如果第一条通过而第二条报veth ... operation not supported,说明 Docker 的虚拟网卡模块没被加载,修复命令是:
sudo modprobe veth如果modprobe报 BTF / 版本不匹配(failed to validate module [veth] BTF),含义是当前运行的内核与磁盘上的内核模块不再对应——通常是一次内核升级发生在系统启动之后。此时重启,重启后模块会自动加载;如果想持久化,执行echo veth | sudo tee /etc/modules-load.d/veth.conf。
值得一提的是,reproduce-issue 技能 的 Preflight 第 2 步也复用了这对检查(A/B 两条命令),并明确指出"这条检查本可以在当年 veth 故障发生时立刻定位问题"。可见网络检查不是形式主义,而是整个预检链条里真实拦截过故障的关卡。
五、第四步:工具链镜像nx-review-sandbox(本技能的核心)
5.1 何时才需要它
需要澄清一个常见误区:镜像仅用于在沙箱内构建未发布的 PR 的 nx(对应 reproduce-verifier 的 Level 2 / reproduce-issue 的 PR 构建模式nx-build)。如果只是针对已发布版本做复现,根本不需要这个镜像——那条路径只需要第 1~3 节加一个公共node镜像即可。
5.2 构建命令:无条件执行,不做存在性检查
bash tools/review-sandbox/build-image.sh不要用"镜像是否存在"来短路这条命令。这是本技能特别强调的一点:任何旧版本构建出来的镜像都能通过存在性检查,于是某个缺失的能力会一直隐形,直到某次审查莫名变慢才暴露——历史上就发生过一次:镜像早于 pnpm store 预热功能构建,导致两个星期内每次审查都要多花约 25 分钟下载依赖包。Docker 的层缓存已经回答了"是否需要重建"这个问题:
- 什么都没变 → 约0.6 秒(全部命中缓存);
- Dockerfile / mise.toml 变了 → 从变更的指令处开始重建;
- pnpm-lock.yaml 变了 → 重跑
pnpm fetch,让预热的 store 始终匹配审查实际安装的 lockfile。
构建脚本 tools/review-sandbox/build-image.sh 的实现还说明了两个工程细节:
- 用镜像 ID 而非日志判断是否重建:脚本用
docker images -q在构建前后各取一次镜像 ID,精确比对("比对日志里有没有 CACHED 字样"是猜,ID 是事实),然后打印一行自解释的结果(sandbox image up to date或sandbox image rebuilt (before -> after)); - 并发安全:
review-pr最多会同时开 5 个并行面板调用它,BuildKit 会对相同的并发构建去重(实测一个 20 秒的步骤在 5 路并发下只真正执行了一次,总耗时约 21 秒而非 100 秒),所以脚本自身无需加锁。
5.3 最小构建上下文:只送 5 个文件
脚本构建时绝不以仓库根目录作为上下文——那会把整个 monorepo(node_modules / .git / dist,动辄数 GB)发给 Docker daemon。它构造的上下文只有 5 个条目、约 2 MB(几乎全是 lockfile):
# 等价逻辑(脚本内部实现): mkdir -p tmp/review-sandbox-ctx cp mise.toml package.json pnpm-lock.yaml pnpm-workspace.yaml tmp/review-sandbox-ctx/ cp -r patches tmp/review-sandbox-ctx/ docker build -t nx-review-sandbox:latest -f tools/review-sandbox/Dockerfile tmp/review-sandbox-ctx这 5 个条目每一个都是承重的,缺一不可。缺失的后果在 Dockerfile 中逐条注释:
| 条目 | 缺了会怎样 |
|---|---|
package.json | corepack 靠packageManager字段激活锁定的 pnpm,缺失时 corepack 会静默激活最新版 pnpm |
pnpm-lock.yaml | pnpm fetch无从解析 |
pnpm-workspace.yaml | 声明了patchedDependencies(及 catalog),缺失则补丁机制失效 |
patches/ | 直接报错中止:ERR_PNPM_PATCH_FILE_PATH_MISSING |
mise.toml | 工具链版本无从锁定 |
另外脚本每次重建上下文目录,避免陈旧的 lockfile 副本残留误导构建。
5.4 工具链的单一事实来源:mise.toml
镜像内的全部工具链由仓库根目录的 mise.toml 驱动——它同时是仓库开发环境自身的工具版本声明,因此镜像与仓库永远保持同步:
[tools] bun = "1.3" java = "24" node = "{{ env['NODE_VERSION'] | default(value='26.7.0') }}" maven = "3.9.11" rust = "1.95.0" vale = "3.13.1" "npm:corepack" = "latest" [tools.dotnet] version = "9" os = ["linux", "macos"]Dockerfile 中的对应逻辑是mise trust+mise install+mise reshim。两个值得注意的点:
- java + dotnet 是硬性需求,因为 nx 仓库在自己的项目图里 dogfood 了
@nx/dotnet和@nx/gradle插件,缺少它们 PR 构建会直接失败; - dotnet 仅对 linux/macos 声明,因为 Windows 上其 vfox 插件存在已知问题。
基础镜像为debian:bookworm-slim,并额外安装了 mise 管理的工具不提供的系统库:nx 的 napi-rs Rust 原生构建需要 C/C++ 工具链(build-essential clang libclang-dev pkg-config libssl-dev python3),.NET 运行时需要libicu72 libgssapi-krb5-2 zlib1g,mise 拉取工具需要curl git unzip xz-utils ca-certificates。
5.5 pnpm store 预热:为什么"暖而只读"
镜像构建的最后一步执行corepack prepare --activate+pnpm fetch --lockfile-dir /work,从 master 的 lockfile 预取约4200 个包进 pnpm 的内容寻址 store,随后删除临时node_modules。
有三个关键设计点:
- store 是缓存不是构建产物:PR checkout 仍然按它自己的 lockfile执行
pnpm install——用不到的条目只是闲置,新增依赖照常拉取。node_modules刻意不烤进镜像:它必须与 PR 的 lockfile 精确匹配,master 的缓存镜像几乎每天都在变,陈旧版本会在最坏方向上失败(对 PR 从未指定的版本跑绿测试); - 暖 store 是只读的:它活在镜像的下层 layer里,容器内首次
pnpm install必须把其 lockfile 触及的部分拷贝上可写层(约 2.3 GB、耗时数分钟)才能硬链接。这就是为什么所有审查共享一个宿主机容器:copy-up 只付一次,第二个审查的安装实测降到约0.39 GB / 12 秒;若每次审查都开独立容器,这 2.3 GB 就得反复付; - 构建产物体积:整套工具链安装加预热 store 耗时较长、占几个 GB(其中暖 store 约 2.6 GB),且要求第 1、3 步先通过——构建需要可用的网络。
六、第五步:冒烟验证
初始化完成后,用一条命令确认沙箱真的隔离、且真的带上了工具:
# RUNTIME="--runtime=runsc" 在 Linux 上 # RUNTIME="" 在 macOS 上 docker run --rm $RUNTIME nx-review-sandbox:latest bash -c ' cd /work # mise 在这里根据 mise.toml 解析版本;切勿用 bash -l(登录 shell 会重置 PATH,丢掉 mise 目录) echo "kernel: $(uname -r)" # Linux+gVisor: 4.19.0-gvisor ; macOS: VM 的内核 mise ls | head node --version; java --version 2>&1 | head -1; dotnet --version '判定"绿灯"的标准:
- 内核不是宿主内核(Linux 上你会看到
4.19.0-gvisor这样的 gVisor 内核版本号,macOS 上则是 VM 的内核)——这是隔离生效的直接证据; node/java/dotnet都能报出版本号——工具链就位。
最后按步骤汇总输出简洁的 ✅/❌,并明确指出用户还需要手动执行什么(通常是带 sudo 的部分)。
七、第六步:磁盘回收与共享状态管理
审查本身会自行清理(sandbox stop删除该次审查的/work/<id>子树,sandbox prune清扫注册表中已无记录的孤儿子树)。但共享状态(即缓存本体)不在它们的清理范围内,需要显式处理:
.claude/tools/sandbox prune --store # pnpm store 清理 + 共享仓库 git gc .claude/tools/sandbox prune --host # 销毁共享宿主机容器;下次 start 会冷重建两条命令在任何沙箱行还存活时都会拒绝执行——那等于删除正在被某次运行中的审查读取的文件。
需要理解的两个细节(对应 .claude/tools/sandbox 的实现):
- 镜像重建后无需手动
--host:共享宿主机的名字由镜像的解析后 ID(而非 tag)派生。build-image.sh是原地重建nx-review-sandbox:latest,若按 tag 取名,后来的审查就会继续使用还跑着旧工具链的宿主机;按镜像 ID 派生则下次start自动拉起新宿主机,被取代的旧宿主机留给prune --host回收它的几个 GB; - 安全边界的真实形态:多个审查共享同一个 pnpm store,一个恶意 PR 理论上可以污染 store、影响后续审查。但这始终发生在容器内部,够不到宿主;它真正的危害上限是破坏后续审查的结论(不是逃逸宿主)。这是该设计的威胁模型决定的:隔离边界是"不可信 PR 代码 vs 宿主",而不是"PR A vs PR B"。
八、与整体工作流的协同:技能不是一次性用品
虽然技能名字带"一次性(one-time)",但它的产物被三个地方反复依赖,构成了一个自我维护的闭环:
- review-pr 技能 的 pre-flight会无条件调用
build-image.sh,因此在每次审查时镜像都被顺带更新到最新——"靠每次审查保持镜像新鲜"取代了"记得手动重跑本技能"; - reproduce-issue 技能 的 Preflight按序检查 Docker、网络(A/B 两条命令)、
runsc、以及仅在 PR 构建模式下的镜像存在性,命中第一个缺失项就停下并打印一行修复建议,大多数缺失项的修复指向本技能——"以 FIX 而非谜题的方式失败"; - reproduce-verifier Agent在 Level 2 前检查
docker image inspect nx-review-sandbox:latest,缺了就提示运行本技能,并且宁可跳过 Level 2 也绝不在宿主上构建。
一句话总结这套初始化的最终形态:Docker 提供容器,gVisor(或 macOS 的 VM)提供隔离,mise.toml 锁定工具链,预热的 pnpm store 让每次审查的安装从"下载 4200 个包"变成"秒级硬链接"——所有审查代码的执行都被压缩进一个可随时销毁、可随时重建的边界之内,宿主自始至终只负责读与编排。
【免费下载链接】nxThe Monorepo Platform that amplifies both developers and AI agents. Nx optimizes your builds, scales your CI, and fixes failed PRs automatically. Ship in half the time.项目地址: https://gitcode.com/GitHub_Trending/nx/nx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考