Claude Code Action 1.0.149 Fixes a Permission Parser Bug That Could Quietly Turn Scoped Bash Into All Bash
Claude Code Action 1.0.149 is the kind of release that should make every CI owner pause before celebrating a green checkmark. Anthropic fixed a parser bug where scoped tool permissions such as Bash(gh:*) or Bash(cat:*) could collapse into bare Bash. In Claude Code terms, that can mean the difference between “the agent may run this narrow class of shell commands” and “the agent has unrestricted shell.” The scary part is not that the workflow would fail. The scary part is that it could keep working, quietly wider than the YAML author intended.
The June 15 release contains two fixes, both about permission parsing in the GitHub Action wrapper. PR #1350 fixes the scoped-tool collapse. PR #1373 fixes a separate divergence between the parser that decided which GitHub MCP servers to install and the SDK parser that decided which tools Claude actually received. Neither bug is glamorous. Both sit exactly on the trust boundary that matters for agentic CI: untrusted code, repository tokens, tool access, secrets-adjacent logs, and an AI system being asked to inspect or modify software.
PR #1350 documents the failure mode with unusual clarity. The action used shell-quote to parse claude_args. That sounds reasonable until the string being parsed is not a shell command but a permission DSL where parentheses and asterisks carry meaning. In shell parsing, unquoted ( and ) are control operators, and barewords containing * can become glob operator objects. A string-only filter then dropped those non-string parse artifacts. The example from the PR is blunt: parsing --allowedTools View,Bash(gh:*),Bash(cat:*) could produce tokens that, after filtering, collapsed to View and bare Bash.
That is silent permission widening. A maintainer writes scoped Bash because they want Claude to comment on a PR, inspect files, or run a narrow command family. The parser strips the parts that made the permission scoped. Claude gets broader shell authority. Because broad Bash subsumes the scoped rules, the workflow may never emit a denial that reveals the mistake. Everything looks healthy. The policy is not.
CI is where permission bugs stop being theoretical
In a local terminal, broad shell access is still risky, but at least the human is usually close to the loop. In GitHub Actions, the agent is running inside automation that may have a checkout, repository write permissions, access to issue and PR APIs, and sometimes deployment-adjacent environment assumptions. Claude Code Action’s own security docs are appropriately paranoid about this environment: prompt injection from hidden markdown, risky pull_request_target usage, write-access triggering, bot allowlists, short-lived tokens, minimal workflow permissions, and scoped tools all matter because the runner is where untrusted prose can become tool execution.
That is why this parser bug is product news, not housekeeping. Agent vendors increasingly sell “let the model work in your repo” as a feature. The contract behind that feature is permission exactness. If the user grants Bash(gh pr comment:*), the system must not interpret that as Bash. If the user denies a narrow tool, it must not accidentally become a blanket deny unless that is explicit. If the wrapper claims to install MCP servers based on granted tools, it must use the same interpretation of the tool list that the runtime uses. There is no useful AI safety story that survives sloppy parsing at the CI boundary.
Anthropic’s fix is pragmatic. PR #1350 replaces shell control metacharacters ( ) | & ; < > with Unicode private-use placeholders before calling shell-quote.parse(), then restores them afterward. Glob operators are mapped back to their literal pattern text. That is not a beautiful abstraction. It is a sensible patch for a live security-sensitive interface: keep the useful quote handling, neutralize the shell semantics that are wrong for the permission language, and add tests so the regression does not come back wearing a different hat.
The test coverage is the part teams should copy mentally. PR #1350 added six regression tests covering unquoted comma-joined and space-separated Bash(X:*) rules, unquoted Tool(content) without glob characters, quoted behavior, mixed tag/user input, and --disallowedTools. The reported suites passed cleanly: base-action at 133 passing tests, the root suite at 706 passing tests, plus typecheck and formatting. For infrastructure that interprets security policy from YAML strings, regression tests are not optional ceremony. They are the product.
Two parsers for one policy surface is one parser too many
PR #1373 fixes a different but related problem. The action had two parsers reading the same conceptual input. parseAllowedTools determined which GitHub MCP servers should be installed. parseClaudeArgsToExtraArgs built the actual SDK tool list granted to Claude. They disagreed on cases involving multiple values after one flag and commented-out lines. The concrete harmful case: --allowedTools "Read" "Grep" "mcp__github__get_commit" could grant the MCP GitHub tool to Claude while the install-decision parser saw only Read, so the GitHub MCP server did not start and the tool failed at runtime.
That case is mostly a reliability failure, but the design smell is bigger. Security-critical parsing logic should not be duplicated unless the divergence is intentional, documented, and tested. In agent systems, “tool granted” and “server provisioned” are two halves of the same policy surface. If they disagree, users get either broken workflows or unnecessary attack surface. MCP makes that especially important because servers are not inert libraries. They route the agent into external systems: GitHub, issue trackers, CI metadata, maybe internal APIs in more customized deployments.
This is also a reminder that scoped tools are only as strong as the weakest adapter around them. Teams often review the visible YAML and feel safe because the config appears narrow. But the real policy is the effective policy after interpolation, shell parsing, action wrapper parsing, SDK option parsing, MCP installation, and runtime enforcement. Every layer is a place where “looks safe” can become “actually broad.” The fix in 1.0.149 removes two of those gaps for Claude Code Action. It should also make maintainers more suspicious of any CI agent integration whose permission story is “just pass a string.”
The practitioner checklist is short. Upgrade to [email protected] or whatever pinned version includes these fixes if you use claude_args, scoped Bash(...), Read(...), WebFetch(...), --disallowedTools, or MCP GitHub tools. Quote --allowedTools values anyway; defense in depth is cheaper than postmortems. Review recent workflow logs for unexpected broad Bash grants. Add a small CI test or dry-run workflow that asserts the effective tool list contains scoped entries, not bare wildcards. Keep GitHub Actions permissions minimal, prefer the default short-lived GITHUB_TOKEN over long-lived PATs where possible, and treat pull_request_target as radioactive unless you have a specific reason and a written threat model.
The larger takeaway is uncomfortable but useful: coding-agent quality now includes parser quality. In 2026, the best agent is not merely the one that writes the best patch or leaves the most convincing review comment. It is the one whose wrappers, permissions, MCP provisioning, and CI defaults preserve the policy the human thought they wrote. A dropped parenthesis should not turn scoped Bash into all Bash. That sentence is boring. It is also exactly what production trust is made of.
Sources: Anthropic Claude Code Action v1.0.149 release, PR #1350, PR #1373, Issue #1357, Claude Code GitHub Actions docs, Claude Code Action security docs