Execution Environment

Agent system authors — where does the agent actually run? Which machine, which directory, which sandbox container? Claude Code's 3 isolation modes (none / worktree / remote) + cross-platform process sandbox + CCR cloud architecture.

Distinct from “permissions”

The previous chapter Permissions covered what can be done — the trust layer of modes / rules / hooks.

This chapter covers where it runs — which machine, directory, sandbox container the agent process lives in. The two questions are orthogonal: one command might be permission-allowed without a sandbox (default CLI mode), or run in a sandbox but rejected by a hook.

The “sandbox” in Zapvol’s vocabulary (backend/infra/sandbox/ — Node / Daytona / E2B) corresponds to this chapter, not the previous one. Two systems for different problems — this chapter keeps them clearly separated.


Three isolation modes

Execution Environment · 3 Isolation Modes + Process Sandbox Orthogonal to permissions: "where it runs" vs "what it can do". Cost increases with isolation strength. isolation strength stronger Default (no isolation) runs in user's current cwd FILESYSTEM User's cwd direct access to entire repo NETWORK User's network same as the CLI itself PROCESS User's machine no extra boundary SETUP COST Zero BLAST RADIUS Shares CLI fate USE WHEN Daily coding, edit current repo Worktree isolation .claude/worktrees/<slug>/ FILESYSTEM Temp git worktree edits isolated, diff then merge NETWORK User's network not isolated at this layer PROCESS User's machine no extra boundary SETUP COST ~1s (git worktree) BLAST RADIUS Files isolated, process not USE WHEN Exploratory edits without pollution Remote (CCR) Claude Code Remote cloud env FILESYSTEM CCR cloud container completely separate fs NETWORK CCR network separate from user's PROCESS Independent Linux container WebSocket callback for progress SETUP COST ~seconds + 6 preconditions BLAST RADIUS Fully isolated USE WHEN Long tasks / batch / audit BASH PROCESS SANDBOX (orthogonal to isolation · via @anthropic-ai/sandbox-runtime) macOS Seatbelt (sandbox-exec) OS built-in · zero install write: cwd + /tmp only network: opt-in per host Linux bwrap + seccomp + socat 3 deps required seccomp blocks unix sockets socat for per-host net filtering Windows Sandbox unavailable bwrap / sandbox-exec POSIX-only relies on permission + hooks real deployment gap Full defense: isolation mode (horizontal) × process sandbox (vertical) × permissions layer (separate chapter)

Claude Code exposes 3 isolation levels via Agent tool (source tools/AgentTool/AgentTool.tsx line 99):

isolation: z.enum(['worktree', 'remote']).optional()
// omitted = default mode (runs in user's current cwd)
// 'worktree' = temporary git worktree isolation
// 'remote' = CCR (Claude Code Remote) cloud execution
ModeFilesystemNetworkProcessBlast radius
Default (no isolation)User’s cwdUser’s networkUser’s machineShares fate with CLI itself
worktreeTemp git worktree (.claude/worktrees/<slug>)User’s networkUser’s machineIsolates file changes, not process
remoteCCR cloud environmentCCR networkSeparate Linux containerFully isolated

Key insight: the three modes represent progressively stronger isolation from easy to hard — but cost increases too. Default is fastest with highest risk; remote is safest but requires several seconds to spin up cloud environment + GitHub app setup.

Task.ts’s 7 TaskType values are orthogonal to this: local_agent runs locally (default or worktree mode), remote_agent runs in CCR, in_process_teammate runs in the same process. TaskType describes what kind of task, isolation describes where the task runs.


Worktree isolation: ephemeral git twin

Creation

Source utils/worktree.ts line 902, createAgentWorktree:

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

  // 1. First: hook-based creation (for non-git VCS like mercurial / sapling)
  if (hasWorktreeCreateHook()) {
    const hookResult = await executeWorktreeCreateHook(slug)
    return { worktreePath: hookResult.worktreePath, hookBased: true }
  }

  // 2. Fallback: native 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)
  // ...
}

Source-level details:

findCanonicalGitRoot — avoid worktree-inside-worktree

Comment (lines 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.

Meaning: if the current process is already inside a session worktree and creates a new subagent worktree, it must go back to the main repo root — otherwise nested worktrees, and the periodic cleanup can’t find them.

Another real production bug fix, not theory.

Slug validation against path traversal

Lines 48-49:

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

validateWorktreeSlug rejects:

  • ../../target (parent directory escape)
  • /absolute/path (absolute path)
  • . / .. segments alone
  • Over 64 chars

The comment explains the strictness: join('.claude/worktrees/', slug) after path normalization, .. can escape the worktrees directory.

Takeaway for your own agent: any user-controllable string destined to become a file path must use strict allowlist validation — not escape / sanitize, but accept only explicitly-safe character classes.

Hook-based fallback: support non-git VCS

When hasWorktreeCreateHook() returns true, a hook path is taken — letting users plug in any VCS (mercurial / sapling / perforce) via WorktreeCreate / WorktreeRemove hooks. The returned path is treated as “an isolation directory shaped like a worktree.”

This is protocol-style extensibility: Claude Code doesn’t deeply bind to git; it defines an interface for “what a worktree should do” and lets users fill in specifics.

Bwrap ghost dotfile cleanup

utils/Shell.ts line 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.

On Linux, bwrap creates 0-byte “mount-point” files in cwd to deny access — after bwrap exits, these become “ghost dotfiles” left behind. Claude Code wrote dedicated cleanup code.

The level of detail shows that production “sandbox execution” is nowhere near a simple exec() — each sandbox type has its own leaks / cleanup / edge cases.

Periodic cleanup

Worktrees have periodic cleanup: scan .claude/worktrees/ for worktrees untouched for 30 days and delete. Implementation has a reuse path:

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.

Fast resume is read-only and doesn’t update mtime — without explicit bumping, resuming an old worktree would cause next cleanup to delete it.


Remote mode: CCR (Claude Code Remote)

Remote isolation runs on a completely different architecture — Claude Code Remote (CCR), ant-only feature.

6 preconditions

tasks/RemoteAgentTask/RemoteAgentTask.tsx’s checkRemoteAgentEligibility + formatPreconditionError defines 6 reasons CCR can’t be used:

ReasonUser message
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.”

Inference: CCR’s architectural dependencies are GitHub code pulling + Claude.ai account identity + enterprise policy gating — not standalone container spin-up. SaaS integration + Anthropic-side runtime, not pure technical isolation.

Launching a remote agent

// AgentTool.tsx lines 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',
    // ...
  })
  // ...
}

After launch, users can fetch a URL via getRemoteTaskSessionUrl(sessionId) to watch the agent’s progress (on claude.ai).

Remote’s three key attributes

  1. Always run_in_background: true — CCR is async; main agent doesn’t wait
  2. Independent session — remote agents have their own sessionId, separate from main agent’s transcript
  3. WebSocket callbackremote/SessionsWebSocket.ts + remote/RemoteSessionManager.ts — main agent receives subagent progress and results via WS

This is not exec-and-wait; it’s message-driven distributed agent architecture.


Bash’s process-level sandbox

Orthogonal to isolation: Bash tool’s own process isolation. Relies on external NPM package @anthropic-ai/sandbox-runtime.

macOS: Seatbelt (sandbox-exec)

  • Apple’s built-in sandbox-exec (Seatbelt)
  • Write limited to cwd and /tmp
  • Network denied by default; explicit opt-in per-host
  • Zero install overhead, macOS ships with it

Linux: bwrap + seccomp + socat

Three separate dependencies (source SandboxDependenciesTab.tsx):

apt install bubblewrap      # bwrap: filesystem isolation (not Docker, lighter)
apt install socat            # socat: network proxy for per-host allow/deny
npm install -g @anthropic-ai/sandbox-runtime   # provides seccomp BPF filter

seccomp BPF is critical — without it, there’s a hole. The source’s diagnostic explicitly says:

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

Without seccomp, attackers can bypass bwrap’s network restrictions via unix domain sockets.

Windows: no sandbox

tools/PowerShellTool/PowerShellTool.tsx line 208:

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

Windows Claude Code has no process-level sandbox. Permission system + deny-list + hooks are the only protection.

This is a real deployment-axis decision:

  • macOS dev: kernel sandbox, can confidently enable permissive modes
  • Linux dev: has sandbox after installing 3 deps; bare without them
  • Windows dev: must rely on permission strategy — sandbox can’t catch things

The source has full SandboxDoctorSection / SandboxDependenciesTab UI components teaching users to install deps — meaning Anthropic is making sandbox diagnostics and onboarding a product-grade feature, not just an error message for devs.

@anthropic-ai/sandbox-runtime is a standalone NPM package

This is a separate NPM package; claude-code/utils/sandbox/sandbox-adapter.ts is only an adapter layer. The package’s responsibilities:

  • Cross-platform SandboxManager abstraction (same API on Seatbelt / bwrap)
  • Parsing SandboxRuntimeConfig (sandbox config in settings.json)
  • Managing SandboxViolationStore (persistence of violation events)
  • Unified SandboxAskCallback (user prompt callback)

Takeaway for your own agent: sandbox capabilities should abstract into a standalone package. Sandbox rules change fast with OS / kernel versions / policy; packaging separately lets sandbox iterate independently without dragging the main product.


Path patterns’ meaning in the sandbox

In Permissions we discussed 4 path patterns (//path / /path / ~/path / ./path). Their actual effect in the sandbox:

  • //pathfilesystem absolute, e.g., //tmp allows access to /tmp
  • /pathrelative to settings file directory — if settings.json is in /home/me/proj/.claude/, /data expands to /home/me/proj/.claude/data
  • ~/path → HOME expansion
  • ./path → relative to cwd

These get converted by resolvePathPatternForSandbox (utils/sandbox/sandbox-adapter.ts) into absolute paths sandbox-runtime understands, then written into bwrap / seatbelt rule files.

Takeaway for your own agent: file path rule syntax should distinguish “absolute” from “relative to config file”. Absolute paths don’t cross machines; config-relative paths do — 80% of dev / CI / production config drift is here.


Process boundary with MCP servers

An easily overlooked boundary: MCP servers are separate processes, not inside Claude Code’s sandbox.

A typical MCP server (e.g., Playwright MCP, GitHub MCP) communicates with Claude Code via stdio or SSE. This means:

  • MCP server has its own permissions (regular process permissions on the user’s machine)
  • MCP server calling external APIs doesn’t go through Claude Code’s sandbox network policy
  • MCP server file reads / writes don’t go through Claude Code’s file sandbox

This is a permission-layer boundary: Claude Code manages invocations of MCP tools via the permission system, but what the MCP server itself does is outside the permission system.

Takeaway for your own agent: subprocesses are new permission boundaries. Your agent can have a perfect internal permission system, but once it spawns a subprocess, the subprocess is fully independent. A common blind spot in compliance audits.


Selection guide for the three modes

ScenarioRecommended modeReason
Daily coding, editing current repoDefaultFastest, edits cwd directly
Exploratory task, want to see changes without polluting current repoworktreeTemp git branch, can diff and merge
Long-running task (1h+), don’t want to tie up local machineremoteRuns in background, pull back on completion
Needs fully isolated env (e.g., external code audit)remoteSeparate container
Batch-process 100 issue-corresponding changesN × remoteParallel
Already a subagent wanting to spawn anotherDefault (fork mode)Avoid nested worktrees

Takeaways for building your own agent

  1. “Permissions” and “execution environment” are two orthogonal things — your design needs both, not just one. Zapvol’s infra/sandbox/ corresponds to this chapter; permission-* to the previous
  2. Isolation should be graded: none (fast) → worktree (file isolation) → remote (full isolation). One-size- fits-all is either too slow or too dangerous
  3. Worktree slug strict allowlist: [a-zA-Z0-9._-]+ validated per segment, reject .. / . / absolute paths
  4. Canonical git root: when creating a worktree from inside a worktree, must go back to main repo — otherwise nested worktrees are missed by cleanup
  5. Hook-based worktree fallback: let users plug in mercurial / sapling / perforce implementations; don’t hard-bind git
  6. Periodic cleanup + mtime bump: unused worktrees should be cleaned up, but resume paths must refresh mtime to avoid deleting in-use ones
  7. Sandbox is platform-specific: macOS built-in, Linux needs 3 deps, Windows has none. Deployment docs must spell out these differences
  8. Componentize SandboxDoctor: make sandbox dependency checks a product-grade UI, not an error message
  9. Abstract sandbox to a standalone package: @anthropic-ai/sandbox-runtime lets sandbox rules iterate independently
  10. Cloud execution (Remote) is SaaS integration architecture: GitHub code pull + identity + policy approval — not pure technical isolation; business integration and technical isolation must be designed together
  11. MCP servers are independent permission domains: your permission system controls invocations of MCP tools; what the MCP server itself does is out of scope — a common compliance blind spot
  12. Ghost dotfile cleanup details show production sandboxes aren’t simple — every sandbox has leaks / cleanup / edge cases; “exec and forget” doesn’t work

Further reading

  • Claude Code source: tools/AgentTool/AgentTool.tsx, utils/worktree.ts (1519 lines), utils/sandbox/sandbox-adapter.ts, tasks/RemoteAgentTask/RemoteAgentTask.tsx, remote/
  • Agent Execution Loop — the Task layer’s 7 task types detailed here
  • Permissions — the orthogonal “what can be done” layer
  • External package: @anthropic-ai/sandbox-runtime
Was this page helpful?