If you’ve tried running Claude Code in GitHub Actions and hit permission errors, this guide shows you exactly how to fix it — with 3 battle-tested patterns.
The root cause is almost always the same: Claude Code is designed for interactive use. It asks for confirmation before writing files, running Bash commands, or touching anything sensitive. In a CI environment, there’s nobody home to click “yes.” The workflow hangs, times out, or exits with a cryptic error.
Here’s what’s actually happening, and how to get past it.
Why Permission Errors Happen in CI Environments
When you run claude -p "do something" locally, Claude Code’s permission system works as intended: it pauses before taking actions and waits for your approval. That’s a feature.
In GitHub Actions, that same behavior is a showstopper. The runner has no interactive terminal. Any prompt that waits for user input will block indefinitely until the job hits its timeout limit.
The specific errors engineers hit most often:
Permission prompt hangs the workflow:
Bash command: npm install
Do you want to proceed?
> 1. Yes
> 2. No
The job never moves past this point.
Interactive mode detection failure:
Error: Cannot use interactive mode in a non-TTY environment
Use --output-format text for non-interactive output
File edit approval required:
Edit file: src/index.ts
This will modify the file. Approve?
All three stem from the same issue: Claude Code’s default mode expects a human in the loop. For CI, you need to tell it explicitly that no human is coming.
Pattern 1: Use the Official claude-code-action (Recommended for Most Teams)
The cleanest solution is Anthropic’s official claude-code-action, which handles non-interactive setup for you. This is the approach to use if you want @claude mentions in PRs and issues, or automated workflows triggered by GitHub events.
Step 1: Add your API key as a repository secret
Go to your repository’s Settings → Secrets and variables → Actions and add:
ANTHROPIC_API_KEY— your Claude API key from console.anthropic.com
Never hardcode this in your workflow file. GitHub will redact secrets from logs, but if you put the key directly in the YAML it will be visible in your repository history.
Step 2: Install the Claude GitHub App
Install from: https://github.com/apps/claude
The app needs Contents, Issues, and Pull requests read/write permissions. These allow Claude to push changes, comment on PRs, and respond to issues.
Step 3: Create the workflow file
# .github/workflows/claude.yml
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
permissions:
contents: write
pull-requests: write
issues: write
jobs:
claude:
runs-on: ubuntu-latest
# Only run when @claude is mentioned
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'issues' && contains(github.event.issue.body, '@claude'))
steps:
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
That’s the minimal working configuration. Once it’s live, you can trigger Claude from any PR or issue comment with @claude implement the changes described in this issue.
Adding custom instructions
For project-specific behavior, put a CLAUDE.md file at the repository root. Claude reads it automatically on every run. You can also pass instructions directly in the workflow:
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: "Review this PR and flag any security issues"
claude_args: "--max-turns 10 --model claude-sonnet-4-6"
The claude_args parameter passes flags directly to the Claude Code CLI. Use it to control turn limits, model selection, and allowed tools.
Pattern 2: Pre-Approve Tools with .claude/settings.json
When you’re running claude -p directly (not via the action), the cleanest way to avoid permission prompts is to pre-approve the tools Claude will need in a .claude/settings.json file checked into your repository.
This approach gives you surgical control: instead of skipping all permission checks, you explicitly list what Claude is allowed to do. It’s the right choice when you want CI to work reliably without opening up unnecessary access.
Basic settings.json structure
{
"permissions": {
"allow": [
"Bash(npm install)",
"Bash(npm run build)",
"Bash(npm run test)",
"Bash(npm run test:*)",
"Bash(git status)",
"Bash(git diff *)",
"Edit(/src/**)",
"Read(/src/**)",
"Read(/tests/**)"
],
"deny": [
"Bash(git push *)",
"Bash(rm -rf *)",
"Bash(curl *)"
]
}
}
Rules are evaluated in order: deny → ask → allow. The first matching rule wins. A deny rule at any level cannot be overridden by an allow rule at a lower level.
Wildcard patterns
The * wildcard matches across arguments, including spaces. A few patterns worth knowing:
| Rule | What it matches |
|---|---|
Bash(npm run *) | npm run build, npm run test:unit, any npm script |
Bash(git * main) | git checkout main, git push origin main |
Edit(/src/**) | Any file anywhere under /src/ |
Read(.env) | .env at any depth in the current directory |
Important: Bash(npm run build) matches that exact command only. Bash(npm run *) matches any npm run invocation. Be specific where you want to be specific.
Permission modes
Beyond individual rules, you can set a default mode in settings:
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(git status)",
"Edit(/src/**)"
],
"defaultMode": "acceptEdits"
}
}
The acceptEdits mode auto-approves file edits and common filesystem commands (mkdir, touch, mv, cp) for paths in the working directory. This cuts down on prompts without fully disabling the permission system.
Running Claude non-interactively with pre-approved tools
# .github/workflows/claude-ci.yml
name: Claude Code CI
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Claude Code
run: npm install -g @anthropic-ai/claude-code
- name: Run Claude Code review
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
claude -p "Review the changes in this PR for correctness and potential bugs.
Output a summary of findings." \
--output-format text \
--max-turns 5
The --output-format text flag switches Claude to non-interactive output mode, which is compatible with CI environments that don’t have a TTY.
Pattern 3: bypassPermissions Mode for Isolated Containers
When your job runs in a fully isolated container with no production credentials and no access to sensitive infrastructure, you can use bypassPermissions mode to skip all permission prompts.
This is the equivalent of --dangerously-skip-permissions in older documentation. The current approach is to set it in .claude/settings.json:
{
"permissions": {
"defaultMode": "bypassPermissions"
}
}
Or pass it via the command line:
- name: Run Claude Code
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
claude -p "Fix the failing tests and update the implementation" \
--output-format text \
--max-turns 20
With bypassPermissions set in settings.json, Claude will execute commands without pausing for approval.
What bypassPermissions still blocks
Even with bypass mode active, Claude Code keeps two circuit breakers:
rm -rf /andrm -rf ~— filesystem root and home directory removal still prompt- Writes to
.git,.config/git,.claude,.vscode,.husky, and similar config directories still prompt (this is a known pain point — see the Known Issues section below)
The safety tradeoff
bypassPermissions is only appropriate when the environment itself provides containment. In practice, this means:
- Running on a fresh GitHub Actions runner (ephemeral, wiped after each job)
- No production credentials in the environment
- No access to sensitive external services
- Workflow permissions scoped to the minimum necessary
A GitHub Actions job on ubuntu-latest typically satisfies these conditions. The runner is a fresh VM, it’s destroyed after the job completes, and you control which secrets it has access to.
What it does not satisfy: running Claude on your local machine with bypass mode and your full credentials in the environment. That’s where people get burned.
# .github/workflows/claude-autofix.yml
name: Claude Code Autofix
on:
push:
branches: [main]
permissions:
contents: write
pull-requests: write
jobs:
autofix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Claude Code
run: npm install -g @anthropic-ai/claude-code
- name: Run automated fix
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
claude -p "Run npm test. If tests are failing, analyze the failures and fix them.
Commit any changes with a descriptive message." \
--output-format text \
--max-turns 15
- name: Push changes
run: |
git config --global user.name "Claude Code Bot"
git config --global user.email "[email protected]"
git push
Bonus: ANTHROPIC_API_KEY Setup and Cost Management
Storing the API key
GitHub Secrets is the only acceptable place for your API key in a CI workflow. The key goes in Settings → Secrets and variables → Actions → New repository secret.
Reference it in workflows:
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
Or as an action input:
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
For organizations running Claude across many repositories, you can store the secret at the organization level and share it selectively across repositories. This is simpler than managing the key in each repository individually.
Controlling costs in CI
Each Claude Code run in GitHub Actions consumes API tokens. On a busy repository, this adds up. The main levers:
Set --max-turns: This caps how many back-and-forth exchanges Claude makes per job. For review tasks, 5-10 turns is usually sufficient. For implementation tasks, 15-20. Without a limit, Claude can run many more turns than you need.
claude_args: "--max-turns 10"
Use workflow concurrency limits: If multiple PRs open simultaneously, you might not want all of them triggering Claude at once.
concurrency:
group: claude-${{ github.ref }}
cancel-in-progress: true
Trigger selectively: The if condition on jobs lets you limit when Claude runs. The @claude mention pattern is useful because it means Claude only runs when explicitly requested, not on every push.
Set job timeouts: Add timeout-minutes to prevent runaway jobs.
jobs:
claude:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
# ...
Which model to use
The default model for claude-code-action is Sonnet. For most CI tasks — code review, test fixing, documentation — Sonnet is the right choice: it’s fast and the cost per run is manageable.
For complex architectural work where you want maximum reasoning quality:
claude_args: "--model claude-opus-4-8"
Opus costs roughly 5x more per token than Sonnet. Reserve it for tasks where the quality difference justifies the cost.
Known Issues with --dangerously-skip-permissions in Recent Versions
If you’re running Claude Code outside the official action and using the --dangerously-skip-permissions CLI flag, be aware of two open bugs as of mid-2026:
Versions newer than v2.1.77 may ignore the flag. Users have reported that bypass mode is effectively broken in recent releases — Claude still prompts for every file edit and Bash command despite the flag being set. The workaround is to pin to v2.1.77 or use .claude/settings.json with "defaultMode": "bypassPermissions" instead of the CLI flag.
Config directory writes still prompt. Even with bypass mode active, writes to ~/.claude/ paths trigger a separate permission category that bypass mode doesn’t cover. This is tracked as a bug in the Claude Code repository. The workaround is to ensure Claude doesn’t need to write to its own config directory during CI runs — which is usually fine for code tasks.
The official claude-code-action handles these edge cases internally. If you’re hitting these bugs with a custom setup, the action is worth switching to.
Choosing the Right Pattern
| Situation | Recommended pattern |
|---|---|
Team wants @claude in PRs and issues | Pattern 1: claude-code-action |
| Automated review on every PR, no trigger needed | Pattern 1 with prompt parameter |
Custom claude -p script in CI, specific tools needed | Pattern 2: settings.json allowlist |
| Fully isolated container, broad automation | Pattern 3: bypassPermissions mode |
| Maximum control over exactly what Claude can touch | Pattern 2: fine-grained allowlist rules |
Most teams should start with Pattern 1. It handles the CI environment setup correctly, plays well with GitHub’s permission model, and is maintained by Anthropic. The settings.json approach (Pattern 2) is useful when you’re building a more custom pipeline. Pattern 3 is for when you need maximum autonomy in a genuinely isolated environment.
Related Reading
If you’re setting up Claude Code in CI, you’ll likely want to read these next:
- Claude Code Best Practices: The Official and Community-Tested Guide for 2026 — covers CLAUDE.md setup, hooks, and workflow optimization that applies directly to CI configurations
- AGENTS.md in CI/CD: Automated Diff Review, Validation, and Drift Detection — how to wire GitHub Actions to keep your AI agent configuration files in sync with the codebase
The permission system documentation is also worth reading directly at code.claude.com/docs/en/permissions — the wildcard pattern reference is particularly useful for writing tight allowlist rules.