DEV Community

Ahab
Ahab

Posted on • Originally published at indieseek.co

Claude Code 2.1.227: Restore Bash in Claude Code Action Without Dropping the Sandbox

Claude Code 2.1.227: restore Bash in Claude Code Action without dropping the sandbox

Quick answer

Claude Code 2.1.227 fixes a regression where every Bash command could fail under claude-code-action on GitHub-hosted runners when allowed_non_write_users was configured. Claude Code Action 1.0.190 pins that fixed CLI generation.

Upgrade, prove the job installed Claude Code 2.1.227+, and check a disposable Bash marker in a separate step. Do not use CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: "0" as the production fix: it disables protections for untrusted-input workflows.

Who this is for

This guide is for maintainers whose Claude Code Action workflow accepts issue or pull-request input from users without repository write access. The recognizable failure is a Bash tool error such as:

bwrap: Can't create file at /home/.mcp.json: Permission denied
Enter fullscreen mode Exit fullscreen mode

Read, Write, or Edit may still appear to work while shell-dependent tests, commits, or pushes never happen. That makes the workflow dangerous to judge from Claude's final narration alone.

Unlike the 2.1.223 permission-regression checklist or 2.1.224 self-hosted environment guide, this task restores the GitHub-hosted action's non-write-user sandbox.

What changed in 2.1.227

Anthropic's 2.1.227 release explicitly says it fixed every Bash command failing under claude-code-action with allowed_non_write_users on GitHub-hosted runners. The corresponding Claude Code Action 1.0.190 source pins Claude Code 2.1.227 and Agent SDK 0.3.227.

The security boundary remains mandatory. allowed_non_write_users bypasses the normal write-access check and requires a github_token input. Official guidance calls it risky, removes secrets from subprocess environments on a best-effort basis, adds PID-namespace isolation on supported Linux runners, and requires the job-scoped GITHUB_TOKEN, minimal permissions, and narrow tools.

Release and protection matrix

Lane Effective version Scrub/isolation Decision
Known bad symptom Claude Code 2.1.216–2.1.220 in the public report Enabled Do not accept narrative success; upgrade
Emergency diagnosis only Any affected build with scrub set to 0 Disabled Confirms coupling, but is not a production fix
Recovery candidate Action 1.0.190 with CLI 2.1.227 Enabled Run all canaries before restoring writes
Production Fixed version pinned by tag or commit Enabled, minimal permissions and narrow tools Ramp from read-only or labeling work

The first row is public reproduction evidence, not an official support range. The shipped fact is the 2.1.227 fix; use the job's install log as truth.

A six-canary recovery

1. Pin the recovery candidate

Use anthropics/claude-code-action@v1.0.190, or pin the reviewed commit. With mutable @v1, record the resolved action commit and CLI on every run.

2. Keep the protection path on

Remove any emergency CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: "0" override. Use only ${{ secrets.GITHUB_TOKEN }} for github_token, never a PAT. Start with the smallest event and permissions: set. For an issue-labeling flow, do not grant contents: write or pull-request writes just because a future workflow might need them.

3. Run a payload-free Bash marker

In a disposable repository or branch, ask Claude to execute exactly one harmless command and read it back:

- uses: anthropics/claude-code-action@v1.0.190
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    allowed_non_write_users: "test-contributor"
    prompt: |
      Run `printf 'CLAUDE_BASH_OK\n' > claude-bash-smoke.txt`, then read the file.
      Do not modify any other file.
    claude_args: |
      --allowedTools "Bash"

- name: Verify Bash executed outside the model narrative
  shell: bash
  run: test "$(tr -d '\r\n' < claude-bash-smoke.txt)" = "CLAUDE_BASH_OK"
Enter fullscreen mode Exit fullscreen mode

Broad Bash permission is acceptable only in this empty, disposable canary. Replace it with command-scoped tools in the real workflow.

4. Prove secrets stay out of child processes

Use fake sentinel credentials, never production secrets. Ask Bash to report only whether known credential variables are absent, and fail if a sentinel value appears in logs, files, comments, artifacts, or the final answer. Keep the parent action's required authentication separate from the child-process assertion.

5. Prove actor and capability denial

Test one named allowed user, one unlisted non-write user, and one bot. Confirm only the named user reaches Claude, and that the job cannot write outside its declared purpose. Avoid allowed_non_write_users: "*"; an allowlist does not make untrusted prompts safe.

6. Gate rollout on external evidence

Require the Bash marker, expected versions, no scrub opt-out, no leaked sentinel, correct actor decision, and expected GitHub result. A green conclusion or confident assistant message is insufficient. If the older CLI is broken, roll back workflow capability instead of the CLI.

Decision tree

  1. If allowed_non_write_users is not configured, investigate a different Bash failure.
  2. If the log shows a CLI older than 2.1.227, update the action or fix a stale pin.
  3. If 2.1.227+ is installed but the marker fails, preserve logs and stop; do not disable scrubbing in production.
  4. If the marker passes but the actor or secret canary fails, reduce permissions and tools before any rollout.
  5. Only restore write-capable automation after all six gates pass on the same action commit and runner image.

Common mistakes

  • Assuming @v1 always means the same bits; record the resolved commit and CLI.
  • Treating an open issue's inferred root cause as the product contract. The official release confirms the fix, not every internal mechanism.
  • Keeping the scrub opt-out because it made Bash work once.
  • Using a PAT with untrusted prompts or granting broad contents: write permissions.
  • Asking Claude whether Bash worked instead of asserting an external marker.
  • Retesting with a real secret, repository, or release credential.

For additional egress controls, pair this recovery with the strict network allowlist checklist. For actor-to-write authority, use the GitHub issue agent approval-confidence checklist.

Copyable acceptance record

action_ref / resolved_commit:
installed_claude_code_version:
runner_image:
allowed_non_write_users: named list | wildcard
subprocess_env_scrub_override: absent | present
workflow_permissions:
allowed_tools:
bash_marker_external_assertion: pass | fail
fake_secret_absent_from_child_outputs: pass | fail
unlisted_actor_denied: pass | fail
expected_write_scope_only: pass | fail
rollback_ref:
owner / evidence_url / decision:
Enter fullscreen mode Exit fullscreen mode

FAQ

Can I fix the failure by setting CLAUDE_CODE_SUBPROCESS_ENV_SCRUB to 0?

It can diagnose the old coupling, but it removes protections intended for untrusted-input workflows. Upgrade and verify 2.1.227+ instead.

Is allowed_non_write_users: "*" safe after the fix?

No. The fix restores Bash execution; it does not make arbitrary contributor prompts trusted. Prefer named users, minimal permissions, narrow tools, and deterministic output validation.

Why check a marker in another step?

The reported regression could leave non-shell tools functioning and still produce a successful-looking answer. The marker proves the command actually ran independently of the model's claim.

Sources

Top comments (0)