bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted
Bubblewrap — the unprivileged Linux sandbox Codex runs commands inside — got
further here than a bad command line would have taken it. It parsed its options,
started building the sandbox, created a fresh network namespace, and then asked
the kernel to bring up the loopback interface inside it. RTM_NEWADDR is the
netlink message that assigns an address to an interface; Operation not permitted is the kernel returning EPERM. Your command never ran. No API was
called, so this is not about your key, your quota, your model or your proxy.
“Loopback” is the most misleading word in the message
It sends people straight to network debugging: DNS, /etc/hosts, firewall
rules, the corporate proxy, whether 127.0.0.1 resolves. All of that is wasted
here.
The interface being configured lives in a network namespace that was created a moment earlier and contains nothing but itself. There is no route, no peer and no name to resolve. What failed is permission to configure an interface, not the ability to reach anything.
That narrows the cause sharply. Configuring an interface requires CAP_NET_ADMIN
in the namespace that owns it — and a process that creates a new user namespace
normally holds a full set of capabilities inside it, which is exactly the
mechanism that lets an unprivileged sandbox exist at all. Getting EPERM here
means that mechanism did not work as bubblewrap expected. Either the
environment does not grant you real unprivileged user namespaces, or an outer
layer is filtering the calls that would have used them.
That second possibility is the one that catches experienced people. If you are already inside a container, your process is inside somebody else’s sandbox, and the outer sandbox’s seccomp profile, capability set, or mandatory access control policy applies to every nested one you try to create. The error is not really about bubblewrap. It is about what the environment you are running in allows.
Where this shows up
Containers, overwhelmingly: Docker and Podman with a restrictive default seccomp profile, rootless container setups, containers nested inside containers, Kubernetes-based CI runners. Then hardened and security-focused distributions that restrict unprivileged user namespaces as a deliberate policy, some WSL configurations, and corporate images built to the same intent. What all of these share is that somebody decided processes here should not be able to create namespaces — and a coding agent that builds a sandbox is doing precisely that.
Is retrying useful?
No. Kernel policy does not fluctuate.
It is not capacity, not a race, not a transient. The same binary in the same
environment gets the same EPERM on every attempt, so a retry loop just prints
the message faster.
The one scenario that produces an inconsistent result deserves attention rather than another attempt: if the same job fails on one run and passes on another, different runs are landing on different machines, images or runtime configurations. That is a fleet-homogeneity finding. Capture which node or image tag succeeded — that is a far more valuable artifact than another failed run, and it usually identifies the exact configuration difference for you.
Which restriction do you actually have
Test whether unprivileged user namespaces work at all. Run
unshare --user --map-root-user true in the same environment. If it succeeds,
user namespaces are available, and something narrower is filtering the
namespace-configuration path — most often a container runtime’s seccomp profile
or a dropped capability set. If it fails with a permission error, user
namespaces themselves are restricted here, and that is the wall.
Establish whether you are inside a container at all. Many people hit this inside a devcontainer, a CI runner or a remote development environment without thinking of it as a container. If you are, the runtime’s defaults are the first suspect and the host’s kernel settings are the second.
Compare host and container. If Codex sandboxes fine on the host and fails inside your image, the restriction belongs to the container runtime’s configuration, not to the kernel policy. If it fails both places, the policy is system-wide.
Compare with a colleague on the same image. Same image and same result means policy; same image and different results means something local — a different runtime version, a different set of runtime flags, a different host.
Fixes, safest first, with their costs stated
Run the agent where namespaces are permitted. A plain VM, a development VM, or a cloud development environment configured for this kind of workload keeps the sandbox intact and changes nobody’s privileges. If you have the freedom to choose where the agent runs, this is the option with no security cost at all, and it is the one most likely to be skipped because it feels like avoiding the problem rather than solving it.
If you administer the host and the restriction is a host policy, the control that governs unprivileged user namespaces varies by distribution and kernel version. Read your distribution’s documentation for the current one rather than pasting a setting from a forum answer, and understand the scope before you change it: relaxing that policy affects every process on the machine, not only this tool. On a personal workstation that may be an easy call. On a shared or production host it is not yours to make alone.
If a container runtime profile is filtering the calls, the change belongs on the runtime side, and the honest framing is this: every widely circulated fix here — privileged containers, added capabilities, an unconfined seccomp profile — widens the outer boundary so that an inner one can exist. In CI and on shared runners, the container is the security boundary; trading it away to gain a nested sandbox is frequently a net loss. If you go this route, make the narrowest change your runtime supports, scope it to the one job that needs it, and write down why.
If you cannot change the environment at all — managed CI, a shared runner,
a locked image — running Codex with its own sandbox disabled and letting the
container be the boundary is a coherent choice, provided the container really is
one. The conditions are concrete: it is ephemeral, it has no long-lived
credentials mounted, its egress is already constrained, and losing it costs
nothing. A container with your cloud credentials or a mounted source tree you
care about does not meet them. Take the current setting names from the Codex
documentation (learn.chatgpt.com/docs, indexed from
developers.openai.com/codex/llms.txt), because sandbox and approval settings
change often and a stale flag can disable more than you intended.
Do not run the agent as root to make this go away. Besides removing the reason the sandbox exists, it frequently does not even work: capabilities dropped by a container runtime are dropped for uid 0 as well, so root inside a restricted container still cannot configure that interface. You would be spending a real security property to buy the same error.
Confirming it, without fooling yourself
The failure mode to avoid is a green run whose sandbox is simply gone. So confirm two things: Codex reports that it is operating with its sandbox, and it successfully executes one trivial command that produces visible output — proving the namespace was built, not just that the error is absent.
Then repeat from a cold start: a fresh container or a fresh CI job created from the same definition, not the warm session you have been poking at. Environment changes made by hand inside a running container disappear on the next run, and a cold repetition is the only test that catches that.
Related sandbox failures
If bubblewrap never reached this stage and complained about its command line instead, that is an unknown option — an old bubblewrap binary, fixed by changing what is installed rather than what is permitted. If the message names the sandbox but no kernel call is involved, a missing sandboxPolicy field is two halves of the tool disagreeing, and loosening any policy will not touch it. The AI coding error triage tool separates the three by which stage of sandbox setup produced the message.