执行环境

Agent 系统作者:Agent 实际在哪台机器的哪个目录跑?Claude Code 的 3 种隔离模式(none / worktree / remote)+ 跨平台进程沙箱 + CCR 云端架构。

和”权限”是两码事

前一章 权限系统 讲的是能不能做——mode / rules / hooks 的信任层。

这一章讲的是在哪里做——agent 进程跑在哪台机器、哪个目录、哪个沙箱容器里。两个问题正交:一条命令可能被权限允许但没沙箱(默认 CLI 模式),也可能在沙箱里跑但被 hook 拒绝。

Zapvol 语境里的”sandbox”(backend/infra/sandbox/ —— Node / Daytona / E2B)对应的是这一章的话题,不是上一章。两套系统解决不同问题,本章把它们分清。


三种 Isolation 模式

执行环境 · 3 种 Isolation + 进程沙箱 和权限正交:"在哪里跑" vs "能做什么"。隔离强度越高代价越大。 隔离强度 更强 默认 (无 isolation) 在用户当前 cwd 跑 文件系统 用户 cwd 直接访问整个仓库 网络 用户网络 和 CLI 本身一样 进程 用户机器 无额外边界 启动代价 故障域 和 CLI 同命运 何时用 日常 coding · 改当前 repo Worktree 隔离 .claude/worktrees/<slug>/ 文件系统 临时 git worktree 改动隔离 · diff 后再 merge 网络 用户网络 本层不隔离 进程 用户机器 无额外边界 启动代价 ~1s (git worktree) 故障域 文件隔离 · 进程不隔离 何时用 探索性改动 · 不想污染 Remote (CCR) Claude Code Remote 云端环境 文件系统 CCR 云端容器 完全独立 fs 网络 CCR 网络 和用户网络分离 进程 独立 Linux 容器 WebSocket 回传进度 启动代价 几秒 + 6 种 precondition 故障域 完全隔离 何时用 长时任务 · 批处理 · 审计 BASH 进程沙箱 (和 isolation 正交 · 通过 @anthropic-ai/sandbox-runtime) macOS Seatbelt (sandbox-exec) OS 内置 · 零安装 写入限于 cwd + /tmp 网络默认拒绝 · per-host 开启 Linux bwrap + seccomp + socat 3 个依赖必装 seccomp 阻止 unix socket socat 做 per-host 网络过滤 Windows 沙箱不可用 bwrap / sandbox-exec POSIX-only 只能靠权限 + hook 保护 真实的部署差距 完整防护:isolation 模式 (横向) × 进程沙箱 (纵向) × 权限层 (另一章)

Claude Code 通过 Agent tool 提供 3 种 isolation(源码 tools/AgentTool/AgentTool.tsx 第 99 行):

isolation: z.enum(['worktree', 'remote']).optional()
// 不传 = 默认模式(在用户当前 cwd 跑)
// 'worktree' = 临时 git worktree 隔离
// 'remote' = CCR(Claude Code Remote)云端执行
模式文件系统网络进程故障域
默认(无 isolation)用户 cwd用户网络用户机器和 CLI 本身同命运
worktree临时 git worktree(.claude/worktrees/<slug>用户网络用户机器隔离文件改动,不隔离进程
remoteCCR 云端环境CCR 网络独立 Linux 容器完全隔离

关键洞察:三种模式由易到难隔离强度递增,但代价也递增——默认最快但风险最大,remote 最安全但要花几秒拉起云端环境 + GitHub app 预设。

Task.ts 的 7 种 TaskType 和这个正交:local_agent 跑在本地(默认或 worktree 模式)、remote_agent 跑在 CCR、in_process_teammate 跑在同进程。TaskType 描述是什么种类的任务,isolation 描述这个任务跑在哪


Worktree 隔离:临时 git 分身

创建

源码 utils/worktree.ts 第 902 行 createAgentWorktree

export async function createAgentWorktree(slug: string): Promise<{
  worktreePath: string
  worktreeBranch?: string
  headCommit?: string
  gitRoot?: string
  hookBased?: boolean
}> {
  validateWorktreeSlug(slug)

  // 1. 优先:hook-based 创建(支持非 git VCS 如 mercurial / sapling)
  if (hasWorktreeCreateHook()) {
    const hookResult = await executeWorktreeCreateHook(slug)
    return { worktreePath: hookResult.worktreePath, hookBased: true }
  }

  // 2. 回退:原生 git worktree
  const gitRoot = findCanonicalGitRoot(getCwd())
  if (!gitRoot) {
    throw new Error('Cannot create agent worktree: not in a git repository...')
  }
  const { worktreePath, worktreeBranch, headCommit, existed } =
    await getOrCreateWorktree(gitRoot, slug)
  // ...
}

几个源码级细节:

findCanonicalGitRoot —— 避免 worktree 套 worktree

注释(第 922-925 行):

findCanonicalGitRoot (not findGitRoot) so agent worktrees always land in the main repo’s .claude/worktrees/ even when spawned from inside a session worktree — otherwise they nest at <worktree>/.claude/worktrees/ and the periodic cleanup (which scans the canonical root) never finds them.

意思:如果当前已经在一个 session worktree 里跑,再创建一个 subagent worktree 时必须回到主仓库根目录——否则 worktree 嵌套,后台 cleanup 就找不着。

这又是一个真实 production 跑出来的 bug fix,不是理论。

Slug 验证防 path traversal

源码第 48-49 行:

const VALID_WORKTREE_SLUG_SEGMENT = /^[a-zA-Z0-9._-]+$/
const MAX_WORKTREE_SLUG_LENGTH = 64

validateWorktreeSlug 拒绝:

  • ../../target(上级目录 escape)
  • /absolute/path(绝对路径)
  • . / .. segment(单独的 dot)
  • 超过 64 字符

注释解释为什么要这么严:join('.claude/worktrees/', slug)路径归一化之后,.. 能 escape 出 worktrees 目录。

给自研 agent 的启示:任何用户可控的字符串如果最终要拼成文件路径,必须做严格白名单验证——不是 escape 或 sanitize,是只接受明确安全的字符集

Hook-based 回退:支持非 git VCS

hasWorktreeCreateHook() 返回 true 时走 hook 路径——让用户用 WorktreeCreate / WorktreeRemove hook 接入任何 VCS(mercurial / sapling / perforce)。回来的 path 被当作”像 worktree 一样的隔离目录”。

这是一种协议式扩展:Claude Code 不深度绑定 git,而是定义了”一个 worktree 应该做什么”的接口,用户补上实现细节。

Bwrap ghost dotfile 清理

utils/Shell.ts 第 386 行:

On Linux, bwrap creates 0-byte mount-point files on the host to deny access to paths inside the sandbox; when bwrap exits as ghost dotfiles in cwd. Cleanup is synchronous and a no-op on non-Linux.

跑 Linux 时 bwrap 会在 cwd 创建 0 字节的 “mount-point” 文件用于拒绝访问——bwrap 退出后这些文件变成 “ghost dotfiles” 留在 cwd。Claude Code 专门写了同步清理代码。

细节多到这个程度,说明生产环境的 “沙箱运行” 绝非简单的 exec() 调用——每一类沙箱都有自己的泄漏 / 清理 / edge case。

清理周期

Worktree 有periodic cleanup:扫 .claude/worktrees/ 下 30 天没动过的 worktree 删掉。实现细节里有一个 reuse 路径:

Bump mtime so the periodic stale-worktree cleanup doesn’t consider this worktree stale — the fast-resume path is read-only and leaves the original creation-time mtime intact, which can be past the 30-day cutoff.

快速 resume 路径只读,不会更新 mtime——如果不显式 bump,resume 过一个老 worktree 后下次 cleanup 就把它删了。


Remote 模式:CCR(Claude Code Remote)

Remote isolation 走的是完全不同的架构——Claude Code Remote(CCR),ant-only 特性。

6 种 Precondition

tasks/RemoteAgentTask/RemoteAgentTask.tsxcheckRemoteAgentEligibility + formatPreconditionError 定义了 6 种不能用 CCR 的原因:

原因用户消息
not_logged_in”Please run /login and sign in with your Claude.ai account (not Console).”
no_remote_environment”No cloud environment available. Set one up at https://claude.ai/code/onboarding?magic=env-setup
not_in_git_repo”Background tasks require a git repository. Initialize git…”
no_git_remote”Background tasks require a GitHub remote.”
github_app_not_installed”The Claude GitHub app must be installed on this repository first.”
policy_blocked”Remote sessions are disabled by your organization’s policy. Contact your organization admin.”

推论:CCR 的架构依赖是GitHub 的代码拉取 + Claude.ai 账户身份 + 企业 policy 审批——不是独立的容器拉起,而是 GitHub integration + Anthropic 侧的运行时。这是一个 SaaS 集成,不是纯技术隔离。

启动一个 remote agent

// AgentTool.tsx 第 433-457 行
if (effectiveIsolation === 'remote') {
  const eligibility = await checkRemoteAgentEligibility()
  if (!eligibility.eligible) {
    const reasons = eligibility.errors.map(formatPreconditionError).join('\n')
    throw new Error(`Cannot launch remote agent:\n${reasons}`)
  }

  const { sessionId, taskId, ... } = registerRemoteAgentTask({
    remoteTaskType: 'remote-agent',
    // ...
  })
  // ...
}

启动后用户可以通过 getRemoteTaskSessionUrl(sessionId) 拿到一个 URL 看这个 agent 跑得怎样(在 claude.ai 上)。

Remote 的三个关键属性

  1. 永远 run_in_background: true——CCR 是异步的,主 agent 不等
  2. 独立 session——remote agent 有自己的 sessionId,脱离主 agent 的 transcript
  3. 通过 WebSocket 回传——remote/SessionsWebSocket.ts + remote/RemoteSessionManager.ts——主 agent 通过 WS 拿到子 agent 的进度和结果

这不是一个 exec-and-wait 模式,是消息驱动的分布式 agent 架构


Bash 的进程级沙箱

和 isolation 正交的一层:Bash 工具本身的进程隔离。依赖外部 NPM 包 @anthropic-ai/sandbox-runtime

macOS: Seatbelt (sandbox-exec)

  • Apple 内置的 sandbox-exec(Seatbelt)
  • 写入权限限于 cwd 和 /tmp
  • 网络默认拒绝,显式开启后按 host 过滤
  • 零安装开销,macOS 出厂就有

Linux: bwrap + seccomp + socat

三个独立依赖(源码 SandboxDependenciesTab.tsx):

apt install bubblewrap      # bwrap:文件系统隔离(不是 Docker,更轻量)
apt install socat            # socat:网络代理,做 per-host 允许/拒绝
npm install -g @anthropic-ai/sandbox-runtime   # 提供 seccomp BPF 过滤器

seccomp BPF 是关键——不装就有漏洞。源码 diagnostic 里明说:

seccomp filter: not installed (required to block unix domain sockets)

不装 seccomp,攻击者可以通过 unix domain socket 绕过 bwrap 的网络限制。

Windows: 无沙箱

tools/PowerShellTool/PowerShellTool.tsx 第 208 行:

On Windows native, sandbox is unavailable (bwrap/sandbox-exec are POSIX-only)

Windows 版 Claude Code 没有进程级沙箱。权限系统 + deny-list + hooks 是唯一的保护层。

这是一个真实的部署决策维度

  • macOS 开发者:有内核沙箱,可以信心地开 permissive mode
  • Linux 开发者:装完三个依赖后有沙箱;没装的裸跑
  • Windows 开发者:必须依赖 permission 策略——沙箱拦不住

源码里有完整的 SandboxDoctorSection / SandboxDependenciesTab UI 组件,教用户装依赖——这说明 Anthropic 真在把沙箱的诊断和引导做成产品级功能,而不是只给开发者看 error。

@anthropic-ai/sandbox-runtime 是独立 NPM 包

这是一个独立的 NPM 包claude-code/utils/sandbox/sandbox-adapter.ts 只是一层 adapter。包的职责:

  • 提供跨平台的 SandboxManager 抽象(同一 API 跑 Seatbelt / bwrap)
  • 解析 SandboxRuntimeConfig(settings.json 的 sandbox 配置)
  • 管理 SandboxViolationStore(违规事件的持久化)
  • 统一 SandboxAskCallback(询问用户的回调)

给自研 agent 的启示沙箱能力应该抽象到一个独立包。沙箱规则随 OS / kernel 版本 / 政策变化非常快,把它圈进一个包能独立迭代,主产品不用跟着发版。


Path Pattern 在沙箱里的含义

前面在 权限系统 讲过 4 种 path pattern(//path / /path / ~/path / ./path)。在沙箱里这些 pattern 的实际作用:

  • //path文件系统绝对路径,例如 //tmp 允许访问 /tmp
  • /path相对于 settings 文件目录——settings.json/home/me/proj/.claude//data 展开为 /home/me/proj/.claude/data
  • ~/path → HOME 展开
  • ./path → 相对 cwd

这些被 resolvePathPatternForSandboxutils/sandbox/sandbox-adapter.ts)转成 sandbox-runtime 能理解的绝对路径,然后写进 bwrap / seatbelt 的 rule file。

给自研 agent 的启示:文件路径 rule 的语法设计要区分”绝对”和”相对配置文件”。绝对路径不跨机器,配置文件相对路径可以——跨 dev / CI / production 的配置漂移 80% 出在这里。


和 MCP Server 的进程边界

还有一个容易忽略的边界:MCP servers 是独立进程,不在 Claude Code 的沙箱里。

典型的 MCP server(比如 Playwright MCP、GitHub MCP)通过 stdio 或 SSE 跟 Claude Code 通信。这意味着:

  • MCP server 有自己的权限(用户机器上的普通进程权限)
  • MCP server 调用外部 API 不走 Claude Code 的沙箱网络策略
  • MCP server 的文件读写不走 Claude Code 的文件沙箱

这是一层权限边界:Claude Code 对 MCP tool 的调用受权限系统管,但 MCP server 自己做什么超出权限系统范围。

给自研 agent 的启示子进程就是新的权限边界。你的 agent 可以有完美的内部权限系统,但一旦 spawn 出一个子进程,子进程完全独立。做合规审计时这是一个常见盲点。


三种模式的选型指引

场景推荐模式理由
日常 coding,改当前 repo默认最快、直接改 cwd
探索性任务,想看改动但不想污染当前 repoworktree临时 git branch,可以 diff 再 merge
长时任务(跑 1h+),不想占用本地remote后台跑,完成后拉回
需要完全隔离的 env(如外部代码审阅)remote独立容器
批处理 100 个 issue 对应的改动N 个 remote并行
已经是 subagent 自己想再起 subagent默认(fork 模式)避免嵌套 worktree

给自研 agent 的要点

  1. “权限”和”执行环境”是正交的两件事——你的设计里不能只有一个,要两个都有。Zapvol 的 infra/sandbox/ 对应这一章,permission-* 对应上一章
  2. Isolation 应该是分级的:none(快)→ worktree(文件隔离)→ remote(完全隔离)。一刀切要么太慢要么太危险
  3. Worktree slug 严格白名单验证[a-zA-Z0-9._-]+ 每个 segment 单独验证,拒绝 .. / . / 绝对路径
  4. Canonical git root:从 worktree 里再创建 worktree 时必须回主仓库——不然嵌套 worktree,清理程序找不着
  5. Hook-based worktree 回退:让用户自己接 mercurial / sapling / perforce 的实现,不要硬绑 git
  6. Periodic cleanup + mtime bump:长时不用的 worktree 要清理,但要有 resume 时的 mtime 刷新机制,避免误删
  7. 沙箱是平台特定的:macOS 内置、Linux 需三个依赖、Windows 无。部署指南要明说这些差异
  8. SandboxDoctor 组件化:把沙箱依赖检查做成产品级 UI,不是 error message
  9. 沙箱抽象到独立包@anthropic-ai/sandbox-runtime 这种独立包能让沙箱规则独立迭代
  10. 云端执行(Remote)的架构是 SaaS 集成:GitHub 拉码 + 身份 + policy 审批——不是纯技术隔离,要把业务集成技术隔离一起想
  11. MCP server 是独立权限域:你对 MCP tool 的调用受权限系统管,MCP server 自己做什么超出范围——合规审计常见盲点
  12. Ghost dotfile 清理等细节说明生产沙箱不简单——每种沙箱都有泄漏 / 清理 / edge case,不能 “exec 完就完了”

延伸阅读

  • Claude Code 源码:tools/AgentTool/AgentTool.tsxutils/worktree.ts(1519 行)、utils/sandbox/sandbox-adapter.tstasks/RemoteAgentTask/RemoteAgentTask.tsxremote/
  • Agent 运行循环——Task layer 的 7 种 task type 在这里展开
  • 权限系统——正交的 “能不能做” 层
  • 外部包:@anthropic-ai/sandbox-runtime
这页有帮助吗?