CtrlK
BlogDocsLog inGet started
Tessl Logo

diagnosing-sbx-sandboxes

Work out why a Docker Sandbox cannot reach a host, install a package, keep a file, or start at all. Use when a download or clone fails inside sbx, a credential is rejected, a sandbox is killed or stops on its own, or something written inside it disappeared.

72

Quality

88%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

Diagnosing sbx sandboxes

Start here, because it settles most of them in one command:

sbx policy log        # what was blocked, and by which rule
sbx diagnose          # everything needed to file a good bug

Fixing what you find usually means editing a kit — see creating-sbx-kits — or the image, see creating-sbx-templates.

The proxy is the whole model

Every packet leaves through a host-side proxy that enforces policy and injects credentials. Configuring the network is configuring the proxy, and most network problems are proxy problems.

A failed install or download is almost always a blocked domain. Blocking surfaces as HTTP 403 with a structured reason in the body, not as a TCP error. Read the body before theorising.

SymptomCause
git clone fails, image looks brokenOnly github.com allowed. Six hosts are needed — api., raw., objects., codeload., release-assets.
Install succeeds but the binary is missingcurl | bash swallowed a 403. Bash exits 0 on empty stdin
API rejects a valid keyThe client bypassed the proxy and sent the raw sentinel
A host service is unreachableAllowlist it as localhost, then address host.docker.internal — the proxy translates
Allowlisted an IP, still blockedAkamai and friends rotate IPs. Allowlist the domain
TLS or binary downloads corruptA wildcard inject[].domain forced interception across CDN subdomains. Name the exact auth host
Rule looks right, nothing worksUnder org governance, kit and local allow rules are inactive — only org rules grant. Deny rules still apply
mount policy denied at creationAn org filesystem rule rejects the workspace path. Usually a * where ** was meant — see governing-sbx-fleets

The sentinel bypass is worth recognising by name. The agent holds a placeholder such as proxy-managed, and the proxy swaps in the real secret in flight — but only for requests that actually traverse it. urllib3 and aiohttp.ClientSession() ignore HTTPS_PROXY, connect direct, and ship the placeholder to the API, which rejects it. Give them trust_env=True.

Check the PROXY column in sbx policy log when credentials are configured and calls still fail. There are two paths: the forward proxy enforces policy and injects credentials; the transparent fallback enforces policy and injects nothing. A process inside a nested Docker container typically lands on the transparent path, so it gets allowed through and then rejected for a bad key.

A corporate TLS-inspecting proxy breaks injection before it starts. Install the internal root CA with update-ca-certificates. Point SSL_CERT_FILE at your CA alone and you replace the system bundle, which breaks the egress path instead.

For one service, a stored secret beats an environment variable. And the sandbox owns HTTP_PROXY, HTTPS_PROXY and NO_PROXY — setting them yourself severs injection and policy.

What policy cannot do: DNS is unfiltered, but UDP and ICMP are blocked in the kernel and cannot be unblocked. A mongodb+srv:// URI needs SRV records over UDP, so it can never resolve inside a sandbox — use a seedlist URI. Non-HTTP TCP works through the same domain rule, but adding :port breaks a TLS service that sends SNI, because the proxy matches the SNI hostname when one is present.

Build-time egress allows 443 and blocks 80.

Confirming what policy is actually in force

Guessing at rules wastes more time than checking them. Three commands settle it:

sbx policy check network api.example.com   # would this be allowed, right now?
sbx policy ls --include-inactive           # rules org governance has switched off
sbx policy ls --wide --source kit          # rule-level detail with IDs, by origin

policy check takes a hostname, host:port, an IP, or a URL, and evaluates a bare host against port 443. Add --sandbox <name> to evaluate in one box's context. It answers the question before you burn a sandbox creation on it.

Inactive rules are hidden by default, which is what makes org governance so confusing from inside: you add an allow, sbx policy ls does not show it, and nothing changes. --include-inactive reveals it with inactive in the STATUS column. A Governance: Managed by <org> line at the top of policy ls is the tell. --source filters by local, org or kit; --decision by allow or deny; sbx policy inspect expands a single rule.

sbx policy reset is not sbx reset. The first deletes only the local policy store and re-prompts for a preset; the second destroys all sandbox state. Both restart the daemon, so running sandboxes stop either way. Reach for the narrow one.

Slow, corrupt, or refusing to clone

Slow git status in a large direct-mode repo is a virtiofs caching question. Caching is on by default everywhere and clone mode always enables it, so this tuning only applies to direct mode. The kill switch exists for the opposite symptom — Git index corruption or file content that reads back wrong:

DOCKER_SANDBOXES_ENABLE_VIRTIOFS_CACHE=0 sbx run <template>

It applies at creation, so recreate the sandbox to change it.

--clone reporting "not in a Git repository" on a valid repo is Git's dubious-ownership check, not sbx. It shows up on Windows against a \\wsl.localhost\... path, where Git refuses a repository owned by another user across the WSL boundary and the detection fails underneath. Confirm with git -C <path> rev-parse --show-toplevel — a detected dubious ownership error names the real cause. Add the repo to safe.directory on the host and retry:

> git config --global --add safe.directory '%(prefix)///wsl.localhost/Ubuntu/home/you/repo'

Killed, stopped, or out of memory

A sandbox auto-stops after roughly 25 seconds idle, and there is no sbx start to bring it back.

Memory is a hard wall, not a soft one. -m is rejected outright above 75% of host RAM — a 48 GB host caps at 36 GiB, and 40g fails with a message saying so. There is no swap and cannot be: the rootfs is overlay, so mkswap succeeds and swapon returns EINVAL. Crossing the limit is an immediate SIGKILL with no thrashing grace period, so size for peak, not for average.

Exit 137 from a kit's install hook is the other SIGKILL: install hooks are killed for slow work. Move the work to startup, or stage it in the image.

Things that quietly do not persist

  • Removing a sandbox destroys everything inside the microVM — packages, images, volumes, agent history. Only the host workspace survives.
  • ~/.claude/settings.json is replaced with a symlink to a root-owned empty directory after your files land, leaving it dead. Everything else copied into the image survives. Only /home/agent/... is seeded over, so a workspace path such as <workspace>/.claude/settings.local.json needs no workaround; otherwise write it from setup.startup, which runs after the seeding.
  • A saved template loses user-level agent config, which is regenerated at every creation — and it captures everything else, including any secret placed by hand.
  • binfmt_misc registration is volatile. Register it in startup, never install.
  • Extra directories mount at creation only, at their absolute host paths: sbx run claude ~/a ~/b:ro. sbx cp moves files into a running box — and anything copied to a non-mounted path costs microVM root disk, which is 20 GB by default and dies with the sandbox.
  • sbx reset deletes stored secrets unless you pass --preserve-secrets.
  • --kit, --clone, --publish and volumes are create-time only. Re-attaching ignores them silently; use sbx kit add / sbx ports, or recreate.
  • Global secrets and --all-sandboxes registry credentials need a recreate. Only sandbox-scoped entries reach a box that already exists.

Disk is sized by environment variable at creation, and the three are independent: DOCKER_SANDBOXES_ROOT_SIZE (20 GB), DOCKER_SANDBOXES_DOCKER_SIZE (50 GB sparse, /var/lib/docker), DOCKER_SANDBOXES_CLONED_WORKSPACE_SIZE.

Reclaiming root disk after the fact. A workspace mount is host storage; the rest of the VM is a 20 GB overlay. Move bulk off the overlay and link it back:

mv /home/agent/big-thing <workspace>/big-thing
ln -s <workspace>/big-thing /home/agent/big-thing

Measured: 200 MB moved this way freed exactly 200 MB of overlay, stayed readable through the link, and left a 71-byte symlink behind. It resolves because direct mode mounts the workspace at the same absolute path inside the VM as on the host — the same property that lets claude --resume work across sandboxes. The link lives on the overlay and dies with the box; the data no longer does.

Git mode surprises

  • Clone mode protects from modification, not inspection. The host repo is mounted read-only including untracked and .gitignored files, so a .env in the working directory is readable by the agent.
  • A host worktree gives the agent no git at all. Only the worktree directory is mounted, so its .git pointer file cannot resolve — files are editable, but status, commit and branch all fail.
  • The shared skills store is read-write and cross-sandbox from 0.37.0. One sandbox can rewrite a skill another later runs. --no-share-skills at creation opts out.

Shells and environment

sbx exec skips ~/.bashrc, so an interactive-only export is invisible to it. The base image supplies BASH_ENV to carry the persistent environment into non-interactive shells; a Dockerfile that sets BASH_ENV itself disables that mechanism silently.

/etc/sandbox-persistent.sh is sourced before every command, including non-interactive ones. Export variables there. Keep shell completion scripts out of it — they depend on COMP_WORDS and friends existing, and sourcing them outside a completion context breaks every subsequent command, usually as silent empty output.

printenv SANDBOX_VM_ID distinguishes host context from sandbox context.

Isolation, honestly

The sandbox isolates the agent from the machine. It does not isolate the repository from the agent — in direct mode, edits land on the host tree immediately. A sandbox does not review a diff.

--branch shares one microVM, so that isolation is git-level, not VM-level.

A reproduced escape exists in public: sbx policy allow run from inside a sandbox landed at global scope. Treat in-sandbox policy commands as untrusted.

When the error names the wrong thing

An io.containerd.transfer.v1 plugin error can be a mkfs.erofs glibc-2.38 bug. The message blames the wrong component.

sbx runs an embedded containerd that the host's docker ps cannot see, at $_MYSBX_SBXD_SOCK.

A containerd "database is at major version 6, but this binary only supports up to major version 1" error means you downgraded sbx. A newer version migrated the local state database to a schema the older binary cannot read. The error blames the backend; the cause is the version swap. Recover with sbx reset --preserve-secrets, which clears the state and keeps your secrets.

Windows drives map to /c/, not /mnt/c/. claude --resume works across sandboxes only because dev boxes bind-mount at an identical absolute path, so it breaks under --clone and on Windows.

When nothing else works

Escalate in this order — each step destroys more than the last.

  1. sbx reset --preserve-secrets — all sandbox state, secrets kept.

  2. Remove the state directory by hand. Stop everything with sbx reset first. macOS is one directory, ~/Library/Application Support/com.docker.sandboxes/. Windows is %LOCALAPPDATA%\DockerSandboxes. Linux is three, following XDG, and missing one leaves the problem in place:

    rm -rf ~/.local/state/sandboxes/ ~/.cache/sandboxes/ ~/.config/sandboxes/

    Substitute $XDG_STATE_HOME, $XDG_CACHE_HOME and $XDG_CONFIG_HOME where they are set.

  3. File it. sbx diagnose --upload sends daemon logs, check results and system information to Docker support and prints a diagnostics ID. Quote that ID in the issue at github.com/docker/sbx-releases/issues so it can be correlated with the bundle — an issue without one is much harder to action.

Last verified

Verified against a research corpus and the sbx docs of 2026-08-07 (re-fetched unchanged on 2026-08-10). Several entries here were found by reverse engineering rather than documentation, so re-test before building on one. sbx --version first; the product reached a second kit schema within four months of launch.

Repository
slurpyb/sbx-agent
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.