DeepDojo.AIPublished 2026-11-13Updated 2026-11-13中文
How to Set Permissions for Claude Code in CI
Working with Claude in an interactive terminal feels free. If you're not sure of the next step, you ask another question. If a document is missing, you add it. After a change, you glance at it on the spot and decide whether to keep going.
A fixed job like "run a nightly check and analyze a few hundred lines of build errors" is a different situation. The input is an existing log file; the output is a summary for an alerting system. If you still have to open a window at midnight and press Enter, you've become a human relay.
Repetitive work with fixed boundaries and formats is a good fit for scripts:
- CI extracts failure stack traces after the unit tests run.
- A local script scans code in bulk and suggests test patches.
- A scheduled job pulls slow-query logs and emits JSON alerts.
The hard part is that once a script holds the keyboard, nobody is there to read the permission prompts. The "allow" you'd click casually in a session will either stall the whole run or get skipped wholesale. Start with read-only analysis, then set permissions to match what the script really does: which tools the model gets, which calls skip approval, which actions are blocked, and finally limits on turns, budget, and concurrency.
01 What a CLI call goes through
When you type claude in a terminal, you're using the CLI, the command-line interface. A call can be broken down like this:
Command-line input
|
v
claude [options] [query]
|
v
Choose a mode: interactive / print
|
v
Load context, model, permissions, MCP, agents
|
v
Produce output: text / json / stream-json
|
v
Shown in the terminal, handled by a script, consumed by CI, or piped onwardThese commands start an interactive session, with or without an opening question:
claude
claude "Explain this authentication flow"When a script needs the result of one request, use print mode, which prints and exits:
claude -p "What does this function do?"You can pipe file contents in, and pass the output on to other tools:
cat error.log | claude -p "Explain these errors and suggest fixes"
claude -p "List the to-do items" | grep "URGENT"To begin with, one distinction is enough: to keep discussing, use claude; to hand the result to a script, use claude -p. Resuming sessions, logging in, managing MCP, background sessions, and plugins can wait until you need them.
02 Interactive mode or print mode
Interactive mode suits work that needs ongoing human judgment: exploring architecture, refactoring step by step, comparing approaches, or having Claude Code read code before proposing a plan. Print mode suits one-off, scriptable tasks: explaining logs, summarizing test failures, emitting JSON, producing fixed-format reports, or analyzing files in bulk.
| Mode | Started with | Traits | Good for |
|---|---|---|---|
| Interactive | claude | Multi-turn conversation, history, Tab completion, slash commands | Exploring, discussing, refactoring, human approval |
claude -p | Single query, pipeable, can emit JSON | Scripts, CI, batch jobs, machine consumers |
Put two tasks side by side. A payment module occasionally double-charges and nobody knows why yet; you need to chase evidence and rule out hypotheses, so an interactive session lets you keep talking. Summarizing the nightly build log has fixed inputs and a fixed purpose, so print mode can hand the result to a script.
Once a print process exits, a fresh request may not have the earlier conclusions. Resume the session or pass in what matters to continue; otherwise it may read the code all over again.
Start unattended work with read-only analysis, log summaries, and formatted output. File edits, deployments, pushes, deletions, and data migrations change real state, so keep a human approval step in front of each.
03 -p exits fast, but it still runs many turns inside
-p (print) exits after printing the result, yet a single call can still include several rounds of reasoning and tool use.
Ask it to "review this function and report risks," and it first calls Read to open the file, then Grep across files when it finds an external helper, maybe runs tests in the terminal, and only then prints its analysis.
So set turn limits and budgets from the point of view of finishing the whole job. A careless --max-turns 1 may cut it off right after it reads the first file, leaving you with half a report.
After the script returns, look back at the tool call log too. When the report says it "ran the tests" or "checked the source," confirm it actually did rather than inferring. Print mode saves you the wait between turns; the investigation still takes every step it took before.
04 Set permissions to match the actual actions
Unattended work has nobody on hand to approve anything, which makes permission boundaries matter more. The CLI offers several controls:
| Flag | What it does |
|---|---|
--tools | Restricts the built-in tools available; "" disables all of them |
--allowedTools | Lists tools that run without a permission prompt |
--disallowedTools | Deny rules: a bare tool name removes it from the available tools; a scoped rule denies only matching calls |
--permission-mode | Starts in a given permission mode |
--permission-mode auto | Lets the automatic permission system judge each call's risk |
--dangerously-skip-permissions | Skips permission prompts; high risk |
Judge the task's risk before configuring anything. Read-only analysis gets read tools only. Tasks that change files or external systems also need explicit allow and deny lists.
Say the unattended task is "review new code every night and produce a report." Set permissions by what it actually does:
- Keep Read, Grep, and Glob for reading code.
- Allow the test command it needs, such as
Bash(npm test *), so nothing waits for approval. - Remove the editing tools with
--disallowedTools "Edit,Write". - Deny pushes and deployments, which aren't part of producing a report.
These examples each demonstrate one setting; together they are not a complete security policy:
# Read-only review
claude --permission-mode plan -p "Review this codebase"
# Only reading and searching
claude --tools "Read,Grep,Glob" --disallowedTools "mcp__*" -p "Find every TODO comment"
# Specific git queries without prompts
claude --allowedTools "Bash(git status *)" "Bash(git log *)"
# Block risky commands
claude --disallowedTools "Bash(rm -rf *)" "Bash(git push --force *)"Back to the nightly report. Ask, item by item: which tools can the model still reach, which calls skip approval, and which capability needs its own restriction? Per the Claude Code CLI reference, --tools filters only built-in tools; MCP tools need their own --disallowedTools "mcp__*". --allowedTools only removes prompts; tools it doesn't list don't disappear. It's the same principle as allowed-tools in a slash command, covered in why a "read-only" Claude Code command still edits.
Now look at the deny list. Listing only rm -rf and force pushes leaves other equivalent commands uncovered. Plan mode is good for analysis first, but external accounts still need their own read-only permissions. Tools, commands, and accounts each cover a different stretch, and tightening one leaves the others to check.
See whether steps that still need approval can be removed from the task; whatever you can't tighten belongs in a supervised workflow. Permissions must cover what the script actually does, not what its name suggests. If a step still waits for approval, move it out of the unattended run; skipping the prompt doesn't make the action authorized.
--dangerously-skip-permissions belongs only in a sandbox or disposable container you can destroy and rebuild. Giving an unattended script that on an everyday machine puts files, remote repositories, production deployments, and core databases at risk, so restrict those actions at the permission layer first.
05 Watch for things that run without a tool call
Permission flags govern the tools the model calls, but some things run on their own as the call starts. The Claude Code headless documentation warns that without --bare, claude -p loads project configuration like an interactive session would: it runs the hooks in .claude/settings.json and connects the servers in .mcp.json, even in a folder you've never trusted, and print mode shows no trust dialog.
So even with the model's built-in tools and MCP switched off, hooks in the configuration may still run. Use a trusted directory and check those side effects separately before calling the analysis read-only.
--bare removes that interference, but look at what it removes along with it: hooks, skills, plugins, MCP, auto memory, and CLAUDE.md are all skipped, and it never reads your subscription login or the system keychain, so the Anthropic API needs ANTHROPIC_API_KEY. Less configuration also changes the test conditions; confirm where rules and credentials come from before deciding to use it.
--no-session-persistence only decides whether a resumable session record is kept. CI that only needs the current result can use it, but don't conclude that disk or external state is isolated too.
06 Give the task only the directories, MCP, and models it needs
Working directories and external tools widen the scope too. The standard approach is to cd into the project before starting, and add other directories only for cross-repo work:
| Flag | What it does |
|---|---|
--add-dir | Adds a visible directory |
--mcp-config | Loads MCP configuration from a file |
--strict-mcp-config | Uses only the MCP configuration from --mcp-config |
# Work in the backend directory while reading the frontend
(cd ./backend && claude --add-dir ../frontend)
# Enable only the specified MCP servers
claude --strict-mcp-config --mcp-config ./configs/strict-mcp.jsonThe parentheses keep the cd to this one call, and relative paths resolve from the new directory. The strict MCP example assumes the config file lists only the servers this run needs. Extra directories and MCP servers widen both context and permissions, so an automation script loads only what the task requires.
Adjust model flags on the same basis. --model picks the model, --fallback-model switches to a backup when the primary is unavailable or overloaded, and --effort sets reasoning depth. Ordinary batch jobs can keep the defaults; change them for complex architecture, debugging, or review work. Pinning a single heavily loaded model can raise the failure rate, and a fallback improves availability, but the fallback still has to be capable of the task.
If a CLI session needs a temporary read-only reviewer, pass a JSON definition with --agents and select it with --agent. Give it only Read, Grep, and Glob in tools, and describe when to use it and how in description and prompt. A temporary definition applies only to that session.
07 Set three limits before running a batch
When files number in the hundreds or thousands, it's easy to hit API rate limits or lose control of spend. Before running the full set, set three limits:
| Control | What it limits |
|---|---|
| Turns | Work per item |
| Budget | Cost |
| Concurrency | How many run at once |
# Process many files, limiting turns and budget per file
find src -name "*.ts" -print0 | xargs -0 -I{} claude -p --max-turns 5 --max-budget-usd 1 "review {}"The numbers are only examples, not recommendations for every task. The docs note that --max-budget-usd checks against a client-side cost estimate, and actual spend can pass the cap, so leave some headroom.
Run a small batch first, look at the failures and the cost, then set the limits. Scaling up right away can make problems you haven't diagnosed harder to untangle. A person still decides whether a failed item should be split smaller or retried.
Environment variables help standardize a script's environment, such as ANTHROPIC_MODEL for the default model and CLAUDE_CODE_EFFORT_LEVEL for reasoning effort. Before running on another machine, trace the script and pin down three things: where it runs, who consumes the output, and which MCP config it uses, written explicitly with cd, --output-format, and --mcp-config. Inject real keys through a secure environment configuration and keep them out of tutorials, repositories, and command templates.
When something fails, start from where it failed:
| Symptom | Check first |
|---|---|
claude not found | Installation, PATH, then reopen the terminal |
| Login or API key fails | Auth status, expiry, and API network access |
| Session not found | Name, ID, account, and whether --no-session-persistence was used |
| Permission denied | --permission-mode, --allowedTools, --disallowedTools; add the minimum the task needs |
| MCP unavailable | Config path, server name, and whether it's enabled for this run |
With permissions set, the results the script receives still need checking: parseable JSON and an error-free call don't make a conclusion trustworthy. That step is covered in three checks before using Claude Code JSON output.
That midnight check can be handed to a script safely when the permissions cover every action it really takes. Any step that still needs a person's approval stays in the daytime, supervised workflow.