Home › Articles

Sandbox blocking npm install in Claude Code, Cursor and Codex

Updated 2026-11-05

In short: when an agent's npm install, pip install or curl fails but the same command works in your own terminal, the sandbox is usually the reason. Each tool's sandbox lets commands reach only the hosts you allow and write only inside the project. Allow the registries you use by name, allow the package cache folder, and leave everything else closed. Turning the sandbox off isn't the fix.

Is it the sandbox? Read the error

Agents often misreport these failures as a bad token, a broken registry or a flaky network. The error text usually tells you which limit you hit:

What you seeWhat it usually means
Could not resolve host, Temporary failure in name resolution, Network is unreachableThe command has no network route at all: Codex with network off, or a tool that ignores the proxy variables in Claude Code's sandbox
403 Forbidden, Tunnel connection failed: 403, or a prompt asking about a hostThe sandbox proxy is working and this host isn't on the allowlist
EROFS, Read-only file system, Operation not permittedThe command wrote outside the folders the sandbox allows, often a cache in your home folder
Works for curl, fails for ssh, psql or a database driverThe tool doesn't use the HTTP proxy, so an allowed host still can't be reached

To check rather than guess, ask the agent to run the probe script below. It prints what the sandbox lets that command reach and write.

The three sandboxes side by side

Claude CodeCursorCodex
On by default?No. Turn it on with /sandbox or sandbox.enabledYes in Auto-review (the default mode); optional in Allowlist; none in Run EverythingYes. The default workspace-write sandbox has no network
Config file.claude/settings.json or ~/.claude/settings.json, key sandbox.cursor/sandbox.json or ~/.cursor/sandbox.json~/.codex/config.toml, or .codex/config.toml in a trusted project
Hosts allowed out of the boxNone. You get asked (in Manual mode) the first timeCursor's default list: npm, PyPI, crates.io, GitHub, Docker Hub and many moreNone until you set network_access, and then every host unless the network proxy is on
Allow a hostnetwork.allowedDomainsnetworkPolicy.allow[features.network_proxy] domains
Allow a folder outside the projectfilesystem.allowWriteadditionalReadwritePathswritable_roots
What it coversBash, PowerShell and Monitor commands onlyTerminal commandsCommands Codex runs; not web search or MCP

Claude Code

1. Know what's blocked

The sandbox is off until you run /sandbox or set sandbox.enabled. Once it's on, a sandboxed command can write only to the working directory, a per-user temp directory and folders you've added. It has no direct network route. Traffic goes through a proxy on your machine that checks each host against your allowed domains, and that list starts empty.

What happens with a host that isn't on the list depends on your permission mode: Manual and acceptEdits prompt you, auto mode refuses unless the command named the host and the classifier approved it (v2.1.271 or later), dontAsk refuses, and bypassPermissions lets it through. Answering Yes allows the host for the session. Yes, and don't ask again saves a WebFetch(domain:...) allow rule to your local settings, and the sandbox picks those rules up as allowed domains.

2. Allow the hosts and the cache folder

In .claude/settings.json (commit it) or ~/.claude/settings.json (just you):

{
  "sandbox": {
    "enabled": true,
    "network": {
      "allowedDomains": ["registry.npmjs.org", "pypi.org", "files.pythonhosted.org"]
    },
    "filesystem": {
      "allowWrite": ["~/.npm"]
    }
  }
}

3. The commands that still fail

When a command fails in the sandbox, Claude may offer to retry it unsandboxed. Your mode decides whether that retry prompts. If you'd rather it never happens, set allowUnsandboxedCommands to false. Also note that if the sandbox can't start (missing bubblewrap or socat on Linux), Claude Code runs commands unsandboxed unless you set failIfUnavailable.

Cursor

1. Know when the sandbox is in play

In Auto-review, the default Run Mode, allowlisted calls run as they are and other shell commands run in the sandbox when they can. If a sandboxed command fails on a sandbox limit, the agent can rerun it outside the sandbox, and the Auto-review classifier checks that rerun. In Allowlist mode the sandbox is optional. Run Everything has no sandbox at all. Cloud Agents don't use Run Modes; they run on their own machine.

Cursor's sandbox starts more open than the other two. In the default network mode, sandbox.json + Defaults, a long built-in list is already allowed, including registry.npmjs.org, pypi.org, files.pythonhosted.org, github.com, crates.io and Docker Hub. So when npm install fails in Cursor, look for a host that isn't on that list: a private registry, a Git dependency on another host, or an API your tests call.

2. Add the hosts it's missing

In .cursor/sandbox.json (one repo) or ~/.cursor/sandbox.json (all projects):

{
  "networkPolicy": {
    "default": "deny",
    "allow": ["npm.pkg.github.com", "api.stripe.com"]
  },
  "enableSharedBuildCache": true
}

3. The commands that still fail

Codex

1. Know what the default blocks

Codex's default workspace-write sandbox lets commands write in the workspace and temp folders, with no network. In our test, every host failed with Temporary failure in name resolution, which is easy to read as a DNS problem. Your home folder is read-only, and so are .git, .agents, .codex and .aws inside the workspace. A git commit inside the sandbox failed on .git/index.lock, so with the default on-request approvals Codex has to ask to run it outside the sandbox, or you commit yourself.

2. Open the network to your registries only

# ~/.codex/config.toml, or .codex/config.toml in a trusted project
sandbox_mode = "workspace-write"
approval_policy = "on-request"

[sandbox_workspace_write]
network_access = true          # off by default
writable_roots = ["~/.npm"]    # npm's cache lives outside the workspace

[features.network_proxy]
enabled = true                 # without the proxy, network_access means every host
domains = { "registry.npmjs.org" = "allow", "pypi.org" = "allow", "files.pythonhosted.org" = "allow" }

Two settings, and both matter:

For one session, pass the same settings as flags:

codex -c 'sandbox_workspace_write.network_access=true' \
  -c 'features.network_proxy.enabled=true' \
  -c 'features.network_proxy.domains={ "registry.npmjs.org" = "allow" }'

The proxy only covers commands Codex runs. Web search, MCP servers and connectors have their own settings.

3. Let installs write their cache

With the network open, npm install still failed in our test:

npm error code EROFS
npm error path /home/box/.npm/_cacache/tmp/***

npm keeps its cache in ~/.npm, outside the workspace. Adding it to writable_roots, as in the file above, fixed it. The docs' example uses an absolute path; "~/.npm" also worked in codex-cli 0.166.0. Do the same for pip (~/.cache/pip) or cargo if you use them.

4. Reach localhost on purpose

With the proxy on, local and private addresses are blocked. Our test server on 127.0.0.1:8765 returned 403 until we added both literals:

domains = { "registry.npmjs.org" = "allow", "localhost" = "allow", "127.0.0.1" = "allow" }

Wildcards don't count as local exceptions. allow_local_binding = true opens local and private addresses more broadly, so use it only if a hostname you need resolves to one.

Codex also has newer permission profiles (marked beta) that describe the same filesystem and network rules as one named profile. Don't mix them with sandbox_mode: if sandbox_mode appears in any loaded config, Codex uses the older settings and ignores the profile.

Wildcards don't mean the same thing

PatternClaude CodeCursorCodex
example.comThat hostThat hostThat host
*.example.comLeading *. is supported; the docs don't say whether it includes example.comSubdomains and example.comSubdomains only
**.example.comNot documentedNot documentedSubdomains and example.com
Port"127.0.0.1:8080"; no port means every portNot documentedPorts are stripped

If you keep one list of hosts for all three tools, write exact hosts. They mean the same thing everywhere.

A probe to run inside the sandbox

Ask the agent to run this with the hosts your project needs, for example python3 sandbox_probe.py registry.npmjs.org npm.pkg.github.com. It has to be the agent that runs it. A command you type in your own terminal isn't sandboxed.

#!/usr/bin/env python3
"""Ask the agent to run this inside its sandbox to see what the sandbox blocks.
Usage: python3 sandbox_probe.py [host ...]   (defaults: registry.npmjs.org pypi.org github.com)
Exit code 1 if a host or an existing cache folder is blocked. Standard library only."""
import os, sys, pathlib, tempfile, urllib.request, urllib.error

HOSTS = sys.argv[1:] or ["registry.npmjs.org", "pypi.org", "github.com"]
FOLDERS = [".", ".git", "~", "~/.npm", "~/.cache/pip", "~/.cargo/registry", "~/go/pkg/mod", "~/.gradle"]

def can_write(folder):
    path = pathlib.Path(folder).expanduser()
    if not path.is_dir():
        return None
    try:
        with tempfile.NamedTemporaryFile(dir=path, prefix=".sandbox-probe-"):
            return True
    except OSError:
        return False

def reach(host):
    req = urllib.request.Request(f"https://{host}/", method="HEAD")
    try:
        urllib.request.urlopen(req, timeout=8)
        return True, "reachable"
    except urllib.error.HTTPError as e:  # the server answered, so the network path works
        return True, f"reachable (HTTP {e.code})"
    except urllib.error.URLError as e:
        reason = str(e.reason)
        if "Tunnel connection failed" in reason:
            return False, f"BLOCKED by the sandbox proxy ({reason})"
        return False, f"NO ROUTE ({reason})"
    except OSError as e:  # timeouts and resets
        return False, f"NO ROUTE ({e})"

proxies = sorted(k for k in os.environ if k.lower() in ("http_proxy", "https_proxy", "all_proxy"))
print("Sandbox markers:")
print("  CURSOR_SANDBOX =", os.environ.get("CURSOR_SANDBOX", "(not set)"))
print("  proxy variables:", ", ".join(proxies) if proxies else "none (direct network, or no network)")

failed = False
print("Write access:")
for folder in FOLDERS:
    ok = can_write(folder)
    if ok is None:
        continue
    print(f"  {'ok     ' if ok else 'BLOCKED'} {folder}")
    failed |= (not ok) and folder.startswith("~/.")  # a read-only package cache breaks installs

print("Network (HTTPS):")
for host in HOSTS:
    ok, why = reach(host)
    print(f"  {host}: {why}")
    failed |= not ok
sys.exit(1 if failed else 0)

"BLOCKED by the sandbox proxy" means add the host to the allowlist. "NO ROUTE" means there's no network at all, or the host doesn't resolve. A blocked ~/.npm or ~/.cache/pip means installs will fail even with the network open. A blocked .git is expected in Codex. In Claude Code's Manual mode, a host that isn't allowed may bring up a prompt while the probe waits.

We ran it in Codex's sandbox (codex-cli 0.166.0 on Linux) with the network off, with only network_access, and with the config above, and outside any sandbox. The results matched what's described in the Codex section. We didn't run it inside Claude Code or Cursor, which weren't installed on the test machine; it only reads standard proxy variables and CURSOR_SANDBOX, which Cursor sets in sandboxed commands.

When to stop allowlisting

If the list of exceptions keeps growing (Docker, databases, SSH, local servers), run the agent inside a dev container and make the container the boundary. Claude Code's docs describe a reference container with an outbound firewall, running as a non-root user, and the CLI refuses --dangerously-skip-permissions when it runs as root. Codex's docs suggest --sandbox danger-full-access inside a container when the container is the boundary, so Codex doesn't try to build a second sandbox. A container still lets a command send out anything inside it, so keep host secrets such as ~/.ssh out of it.

For fewer prompts without widening the sandbox, see how to stop Claude Code, Cursor and Codex asking permission for every command. To keep secrets out of reach, see how to stop agents reading your .env file.

A ten-minute setup

  1. List the hosts your installs and tests need: the package registries, any private registry, Git hosts for Git dependencies, and APIs your tests call.
  2. Claude Code: /sandbox, then add those hosts to allowedDomains and the cache folder to allowWrite.
  3. Cursor: leave the network mode on sandbox.json + Defaults and add only the hosts that aren't on Cursor's default list.
  4. Codex: turn on network_access and [features.network_proxy] with a domains table, and add the cache folder to writable_roots.
  5. Have the agent run the probe. Fix what it reports, commit the project files, and keep personal hosts in your home-folder config.

FAQ

Why does npm install fail when my AI agent runs it?

Usually the agent's sandbox, not npm. Codex's default workspace-write sandbox has no network, Claude Code's sandbox only reaches hosts on its allowlist (empty at first), and Cursor's sandbox, in its default network mode, only reaches its built-in list plus your sandbox.json. Even with the network open, npm writes its cache to ~/.npm, which is outside the workspace, so the install can still fail with EROFS until you allow that folder.

How do I allow a domain in the Claude Code sandbox?

Add it to sandbox.network.allowedDomains in .claude/settings.json or ~/.claude/settings.json, or answer "Yes, and don't ask again" when Claude Code asks, which saves a WebFetch(domain:...) allow rule to your local settings. Sandboxed commands also get every domain from WebFetch(domain:...) allow rules.

Where does Cursor's sandbox.json go?

In ~/.cursor/sandbox.json for every project, or .cursor/sandbox.json in the repo for one project. Both files are merged and the project file wins. Network allow lists from both are combined, and "deny" as the default wins over "allow".

How do I give Codex internet access for package installs?

Set network_access = true under [sandbox_workspace_write] in config.toml. On its own that opens every host, so also turn on [features.network_proxy] with a domains table that allows only your registries. Add writable_roots for the package manager's cache folder if installs fail with a read-only error.

Why can't the agent reach my dev server on localhost?

All three sandboxes treat local addresses as special. Codex blocks them until you allow the exact literal ("localhost" or "127.0.0.1"). Cursor blocks 127.x and private ranges by default. Claude Code on Linux gives each sandboxed command its own localhost, so it can't see servers on your machine; on macOS, set network.allowLocalBinding to true.

Sources

Official docs, checked 2026-11-05.

Get the free rules sampler + new tips by email

Confirm your email and we'll send you straight to the sampler download. Then one short email every two weeks covering tool format changes (Cursor, Claude Code, Codex, Windsurf, Copilot), new rules and kit updates. No spam.

We store only your email, with our email provider Resend. See the privacy policy.

Unsubscribe anytime from any email. Rather skip email? Get the sampler on GitHub or download the zip directly.