Skip to content

Claude Code sandbox (bwrap) cannot start in workers: /proc/sys is read-only #528

Closed

Description

@kyle-sexton

Problem

Claude review lanes on ci-runner workers cannot start the Claude Code sandbox. Every Bash call fails with:

bwrap: cannot open /proc/sys/user/max_user_namespaces: Read-only file system

Reported in melodic-software/ci-workflows#938 (lane comments on melodic-software/dotfiles#1383-#1394 since 2026-10-30, and melodic-software/provisioning#882's security lane). As a workaround, ci-workflows pinned claude-code-action at v1.0.253 (melodic-software/ci-workflows#939), held by an ignore at .github/dependabot.yml:34 there. The pin and the ignore can go once this is fixed.

Docker mounts /proc/sys read-only in every container. The userns seccomp profile (README "seccompProfile") allows unprivileged user namespaces, but no worker setting changes /proc/sys's mount, and README.md:366-367 says none can add privileges.

Cause: UNVERIFIED. It looks like the bwrap that newer claude-code-action releases run writes /proc/sys/user/max_user_namespaces (for example to disable nested user namespaces), and that write needs /proc/sys writable.

Expected

The Claude Code sandbox starts in a worker on a target with seccompProfile: userns, without privileged mode, the Docker socket, or added capabilities.

Fix

Confirm which bwrap invocation opens the file and why. Then pick the narrowest change:

  • a worker setting that leaves /proc/sys writable only inside the job's own user namespace;
  • a sandbox option on the caller side that skips the write;
  • a change upstream.

Any option that widens the worker boundary is an owner decision.

Related

Activity

  1. kyle-sexton commented on Nov 8, 2026

    @kyle-sexton
    MemberAuthor

    Cause (confirmed from source)

    • The failing write is bubblewrap's --disable-userns. It opens sys/user/max_user_namespaces with O_WRONLY and dies with exactly our message when the open fails (bubblewrap.c:3395-3405). Docker's default ReadonlyPaths includes /proc/sys (moby defaults.go:110-116), so the open fails in every worker.
    • sandbox-runtime passes --disable-userns only when no apply-seccomp helper is in use (linux-sandbox-utils.ts:3847-3860). That code's own comment says bubblewrap "dies on the read-only /proc/sys of an unprivileged container". The behaviour came from sandbox-runtime#588.
    • First failing release: claude-code-action v1.0.254 (Claude Code 2.1.304). The --disable-userns string occurs 0 times in the 2.1.303 binary and 3 times in the 2.1.304 binary.
    • Why no helper: a native Claude Code build finds apply-seccomp only in its own bundled vendor paths or in a global npm install of @anthropic-ai/sandbox-runtime (vendor/seccomp/<arch>/apply-seccomp) (generate-seccomp-filter.ts:194-330). When it finds neither, it logs "apply-seccomp binary not available - unix socket blocking disabled. Install @anthropic-ai/sandbox-runtime globally for full protection." (linux-sandbox-utils.ts:3624-3629).

    Fix

    Install @anthropic-ai/sandbox-runtime globally, at a pinned version, in the lane workflow, next to the existing bubblewrap and socat install (melodic-software/ci-workflows pr-review.yml, around line 815). Claude Code's sandboxing docs give the same instruction.

    • With the helper in the chain, bubblewrap gets no --disable-userns.
    • The helper's own write to max_user_namespaces is non-fatal. Its seccomp filter then blocks nested user namespaces and AF_UNIX sockets (apply-seccomp.c:885-896).
    • So the sandbox gains protection and the worker boundary does not change.
    • One install covers both the self-hosted and the GitHub-hosted lanes.

    Rejected:

    • sandbox.allowNestedUserNamespaces: ignored under CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 (settings reference).
    • enableWeakerNestedSandbox: bind-mounts the container's /proc into the sandbox, which works against the scrub's PID isolation.
    • Docker ReadonlyPaths overrides or systempaths=unconfined: these widen the worker boundary.

    UNVERIFIED until a worker lane run proves it:

    • that the helper's nested unshare passes the userns seccomp profile;
    • that a sandbox-runtime version pinned apart from Claude Code's bundled one works with that Claude Code.

    No ci-runner code change is expected. This issue stays open until a self-hosted lane on the latest claude-code-action runs Bash successfully.

  2. kyle-sexton commented on Nov 8, 2026

    @kyle-sexton
    MemberAuthor

    Fixed in melodic-software/ci-workflows with no ci-runner change:

    Evidence from review lanes on ci-runner workers on the latest action, both successful:

    • provisioning run 42499839401, on ci-runner-melo-desk-001, and provisioning run 42499720854, on ci-runner-melo-lap-001.
    • Both runs log installed apply-seccomp from sandbox-runtime 0.0.81 at /usr/local/lib/node_modules/@anthropic-ai/sandbox-runtime/vendor/seccomp/x64/apply-seccomp.
    • cannot open /proc/sys/user/max_user_namespaces occurs 0 times in either log.

    With the helper in the chain, sandbox-runtime passes no --disable-userns, so bubblewrap never writes the read-only /proc/sys (cause and source lines in the comment above).

  3. kyle-sexton commented on Nov 8, 2026

    @kyle-sexton
    MemberAuthor

    Reopening. The evidence I closed this on does not show that Bash runs inside the review session.

    • The check I used can't see the failure. "cannot open /proc/sys/user/max_user_namespaces occurs 0 times" was read from the job logs. The logs do not include Claude's tool results, so a Bash failure inside the review never shows up there.
    • Both review lanes on melodic-software/provisioning#952 report a failing shell. They ran on ci-runner-melo-desk-001 at head cdebbe7 (run 42499720828), with claude-code-action 61a9c274 (v1.0.269) and installed apply-seccomp from sandbox-runtime 0.0.81.
      • claude-review: "The Bash tool failed in this session (the sandbox could not start), so I ran no commands."
      • claude-security-review: "The sandbox's Bash tool failed (bwrap, read-only /proc)."
    • The workflow's own probe passes and doesn't test this path. The probe runs only bwrap --unshare-pid --dev-bind / / true, and it passed ("bwrap created a PID namespace; the subprocess sandbox can start"). The helper's path is never exercised.
    • A likely second cause, UNVERIFIED. In sandbox-runtime, the apply-seccomp nested-userns path writes /proc/self/setgroups and uid_map. Without a fresh --proc, --ro-bind / / leaves /proc read-only and those writes fail with EROFS (linux-sandbox-utils.ts:3835-3838).

    Next step: capture the review session's actual Bash tool error, either from the execution file or from a run with SRT_DEBUG=1 that prints tool results.

  4. kyle-sexton commented on Nov 8, 2026

    @kyle-sexton
    MemberAuthor

    Third data point: the claude-review on melodic-software/provisioning#952 at head b33ba5d (after the v1.0.269 pin and the apply-seccomp helper install) also says "Bash was unavailable in this sandbox, so I did not run the Pester tests or ci-runner config validate." The fault persists on current ci-workflows.

  5. kyle-sexton commented on Nov 8, 2026

    @kyle-sexton
    MemberAuthor

    Verified fixed on v0.1.38 workers with ci-workflows #1054 (full sandbox) and #539 (seccomp rule). Tool-call lines from ci-workflows #1056:

    Closing. ci-workflows#938 is closed with the same evidence.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions