Beyond the roadmap: Claude Code without you in the room. Days 1 to 10 were about working with Claude Code in a conversation. Today it runs on its own: from a script, from a build, or on GitHub when someone comments on a pull request. Same tools, same rules from Day 5, no chat window. The big rule of the day is simple: the less you watch, the tighter the guardrails must be.
01The big picture: from conversation to command
What. Until now you typed to Claude Code and read its answers. Headless mode (also called print mode) turns it into an ordinary command: you give it a prompt, it does the work, prints the result and exits. That makes it something a script, a scheduled job or a build server can call, just like git or npm.
Why it matters. Plenty of useful work does not need you watching: explain this failing build log, check my diff for typos, review every pull request, write a daily summary of what changed. Doing these by hand is easy to forget. Automating them means they happen every time, for every teammate.
claude -p command: input, permissions, output you can parse, reproducible runs, conversations, and cost limits.@claude in issues and pull requests, review PRs automatically, and run on a schedule.02What headless mode is, in plain words
What. Think of two ways to use a calculator. You can press buttons and watch the screen (interactive), or you can hand someone a slip of paper with the sum and get a slip back (headless). The flag -p (short for --print) means "take this one prompt, print the answer, stop." There is no back and forth, so nobody can answer a question or click Approve halfway through.
Why that last point matters. In a normal session, when Claude wants to run a command you have not allowed, it asks you. In a headless run nobody is there to ask. So you decide in advance what Claude may do, using the permission ideas from Day 5. Most of this article is about making that decision well.
03Your first claude -p
How. Open a terminal in a project (in VS Code, Ctrl+`) and run:
claude -p "What does the auth module do?" # let it edit files too: name the tools it may use claude -p "Find and fix the bug in auth.py" --allowedTools "Read,Edit,Bash" # did it work? 0 means success, anything else means failure echo $LASTEXITCODE
Claude Code exits with code 0 on success and a non-zero code when the run fails, so a script can branch on it ($LASTEXITCODE in PowerShell, $? in bash). If you pass an invalid flag you get an error on stderr before anything runs. Some failures happen inside the run, such as missing sign-in: those are printed as the result on stdout, so read the output as well as the exit code in your scripts.
Use the quotes your shell expects. The examples use double quotes, which work in PowerShell, cmd and bash. Inside a double-quoted PowerShell string, a $ starts a variable, so put prompts that contain $ in single quotes.
04Pipe data in, redirect the answer out
What and why. A headless run reads standard input, so you can feed it a log, a diff or a file the same way you would feed grep. This is often better than letting Claude go and fetch the data itself: it needs fewer permissions, and you control exactly what it sees. How:
# bash / macOS / Linux cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txt # PowerShell Get-Content build-error.txt | claude -p "concisely explain the root cause of this build error" | Set-Content output.txt # the diff of your branch against main git diff main | claude -p "list any typos, one per line as file:line then the issue"
Piped input is capped at 10 MB. Beyond that Claude Code exits with a clear error and a non-zero code. For bigger inputs, write the data to a file and mention the path in the prompt. Piping a diff also means Claude needs no permission to run git itself.
05Letting it act: permissions when nobody is watching
What. By default a headless run starts in Manual mode: anything that would need your approval is simply denied, because nobody can click yes. Reading files in the project and the usual read-only commands still work. To let it do more, you grant permission up front. You have three levers, all from Day 5:
"Read,Edit" allows reading and editing. For shell commands use a pattern: "Bash(git diff *)" allows any command that starts with git diff. The space before the star matters: without it, git diff* would also match git diff-index.acceptEdits: file edits and common filesystem commands (mkdir, mv, cp) go through. dontAsk: anything that would prompt is denied, the safest choice for locked-down CI. auto: a classifier reviews most actions for you.Why this is the heart of automation. Every allowed tool is something a robot can do at 3 a.m. with nobody watching. The rule of thumb: start with the smallest list that completes the job, run it, see what was denied, and add only that. In stream-json output, denials show up as permission_denied messages, and the final result lists them in permission_denials, so you can see exactly what to consider adding.
# write a commit message and commit, and nothing else claude -p "Look at my staged changes and create an appropriate commit" ` --allowedTools "Bash(git diff *),Bash(git log *),Bash(git status *),Bash(git commit *)" # apply lint fixes; file edits allowed, shell still restricted claude -p "Apply the lint fixes" --permission-mode acceptEdits # locked-down unattended run claude -p "Update the dependency pins and run the tests" ` --permission-mode auto --permission-prompts none
The backtick ` at the end of a line continues the command in PowerShell. In bash use a backslash \ instead.
06Output a program can read
What. By default you get plain text, which is great for people and awkward for programs. --output-format changes that. text is the default. json returns one JSON object with the answer in a result field plus the session id and usage details. stream-json sends one JSON event per line as it works, for live progress.
Why. A script that has to parse a paragraph of prose will break. A script that reads a field will not. With JSON you can pull out the answer, check the cost, or continue the same conversation later.
# bash with jq: just the answer text claude -p "Summarize this project" --output-format json | jq -r '.result' # PowerShell, no extra tools needed $out = claude -p "Summarize this project" --output-format json | ConvertFrom-Json $out.result $out.total_cost_usd # estimated spend for this run
Force an exact shape. Add --json-schema with a JSON Schema and the reply is checked against it, with the structured data in a structured_output field. Always check that the field exists before using it: if Claude declined the request, it is missing.
claude -p "Extract the main function names from auth.py" `
--output-format json `
--json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' `
| jq '.structured_output'
# result: the list of function names
For live output use --output-format stream-json --verbose --include-partial-messages. The last line of the stream is a result message with the final text, cost and session details. The cost numbers are client-side estimates and can differ from your bill.
07Reproducible runs: --bare
What. A normal claude -p loads the same things an interactive session would: your CLAUDE.md, hooks, skills, subagents, plugins, MCP servers and memory. --bare skips all of that auto-discovery and starts faster. Claude then has just the Bash, file read and file edit tools.
Why it matters. A script that behaves differently on every laptop is a bug factory. A hook in one teammate's home folder or an MCP server in a project's .mcp.json can change what a run does. --bare makes the run depend only on what you pass in the command. It is the recommended mode for scripted calls and is expected to become the default for -p in a future release. One more reason: without it, a -p run in a repository you have never opened runs that project's hooks and connects its MCP servers with no trust prompt, so be careful pointing it at code you did not write.
How. Bare mode never reads your sign-in, so set an API key. Pass anything you do want with flags:
$env:ANTHROPIC_API_KEY = "sk-ant-..." # create a key in the Claude Console claude --bare -p "Summarize README.md" --allowedTools "Read" # bring back only what you need claude --bare -p "Review this change" ` --append-system-prompt "You are a strict reviewer." ` --mcp-config .\ci-mcp.json --plugin-dir .\team-standards
--append-system-prompt or --append-system-prompt-file.--settings <file-or-json>.--mcp-config <file-or-json>. Only servers named here connect.--agents, --plugin-dir (the Day 10 plugin flag works here too), --plugin-url.Never type a real key into a file you commit. Keep it in an environment variable, and in CI in the platform's secret store.
08Multi-step runs: continue and resume
What and why. Each claude -p is a fresh start unless you tell it otherwise. For a review that needs follow-up questions, continue the earlier conversation so Claude keeps its context (Day 5). How:
# continue the most recent conversation claude -p "Review this codebase for performance issues" claude -p "Now focus on the database queries" --continue # several conversations at once: capture the session id # bash session_id=$(claude -p "Start a review" --output-format json | jq -r '.session_id') claude -p "Continue that review" --resume "$session_id" # PowerShell $id = (claude -p "Start a review" --output-format json | ConvertFrom-Json).session_id claude -p "Continue that review" --resume $id
You can run the two commands from different folders, because Claude Code finds a session by its id across projects on this machine.
09Skills, commands and system prompts in headless runs
What and why. Everything you built earlier still works headless, which is what makes automation powerful. Your Day 8 skill becomes a one-line job. How:
/skill-name in the prompt, for example claude -p "/review". Claude Code expands it before running. From a plugin: /team-standards:review.--append-system-prompt "You are a security engineer" adds your rules on top of the defaults. --system-prompt replaces the defaults entirely, which also drops the git status snapshot./login, are not available. A few take a value as an argument, such as /model sonnet. To change a setting, pass /config key=value.10Four small recipes you can use today
What and why. Automation pays off in small, boring jobs. Each recipe below is a few lines. How:
{
"scripts": {
"lint:claude": "git diff main | claude -p \"you are a typo linter. for each typo in this diff, report filename:line on one line and the issue on the next. return nothing else.\""
}
}
Run it with npm run lint:claude. The escaped double quotes keep it working on Windows.
gh pr diff "$1" | claude -p \ --append-system-prompt "You are a security engineer. Review for vulnerabilities." \ --output-format json | jq -r '.result' # run with: bash review.sh 123
npm run build 2>&1 | Tee-Object build.log if ($LASTEXITCODE -ne 0) { Get-Content build.log | claude -p "explain the root cause in 3 lines and the fix" | Set-Content why.txt }
$r = claude --bare -p "Does this diff change a public API? Answer only with the word YES or NO." ` --allowedTools "Read" < diff.txt if ($r -match "YES") { Write-Host "needs an API review" }
Recipe 4 uses Claude as a judge in a pipeline. Treat the answer as a hint that routes work to a human, not as a decision that ships code.
11Limits: budget, turns and stopping a run
What and why. An unattended run that goes in circles costs money and time with nobody noticing. Put a ceiling on every automated run. How:
claude_args.--output-format json the reply includes total_cost_usd and a per-model breakdown, so a script can log spend. It is an estimate, not your bill.Wrap scripted runs in your platform's own timeout as well (a CI job timeout, or the Windows Task Scheduler stop setting), so a hung run cannot hold a machine forever.
12What GitHub Actions is, in plain words
What. GitHub Actions is a robot that lives in your repository. You write a workflow, a YAML file in .github/workflows/, that says "when this happens (a comment, a new pull request, 9 a.m.), start a fresh computer, check out the code and run these steps." The Claude Code GitHub Action (anthropics/claude-code-action) is one of those steps: it runs Claude Code inside that fresh computer.
Why it matters. It moves Claude Code from your laptop to where the team already works. Anyone can mention @claude on an issue and get a pull request back. Every pull request can get a first review before a human opens it. It also means Claude runs on GitHub's machines using your repository secrets, so Part 3 on guardrails applies double.
prompt. Claude waits for the trigger phrase (@claude by default) in an issue, a PR comment or a PR review, then answers in a comment and updates it as it works.prompt. Claude runs on any GitHub event, including a schedule, with no mention needed. By default results go to the workflow run log unless the prompt tells it to post and it has a tool that can.13Set it up: quick and manual
What and why. Setup does three things: installs the Claude GitHub App on the repository, stores your credential as a repository secret, and adds a workflow file. You need admin access to the repository. How, the quick way:
# prerequisites: GitHub CLI installed and signed in gh auth login # then, in a Claude Code session inside your repository > /install-github-app
Follow the prompts. Claude Code installs the app, saves the secret (ANTHROPIC_API_KEY for an API key, or CLAUDE_CODE_OAUTH_TOKEN for a subscription token), pushes a branch with the workflow files you select, and opens GitHub with a pull request ready to create. Create and merge that PR and @claude works. This works for github.com repositories only. For GitLab there is a separate GitLab CI/CD integration.
The manual way, when the command fails or you want full control:
ANTHROPIC_API_KEY (a key from the Claude Console) or CLAUDE_CODE_OAUTH_TOKEN (run claude setup-token locally; available on Pro, Max, Team and Enterprise plans). Never commit a key into the repository.examples/claude.yml from the anthropics/claude-code-action repository into .github/workflows/. If you use the OAuth token, change the anthropic_api_key line to claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}.@claude on an issue or pull request.Installing the app grants its full permission set (it is shared with other Claude features such as Code Review). If your organisation wants only what the Action uses, create a custom GitHub App as the setup guide describes.
14@claude in issues and pull requests
What and why. This is the friendliest first workflow: nobody has to learn a command. Someone writes @claude fix the TypeError in the dashboard on an issue, and Claude reads the code, makes the change and opens a pull request. How: this workflow runs in interactive mode:
name: Claude Code on: issue_comment: types: [created] pull_request_review_comment: types: [created] jobs: claude: if: contains(github.event.comment.body, '@claude') runs-on: ubuntu-latest permissions: contents: write pull-requests: write issues: write id-token: write actions: read steps: - uses: actions/checkout@v6 with: fetch-depth: 1 - uses: anthropics/claude-code-action@v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
@claude, which saves minutes.id-token: write is needed for the default GitHub App sign-in, and actions: read lets Claude read CI results on PRs.@beta, migrate: change it to @v1, replace direct_prompt with prompt and move options like model into claude_args.Try requests like @claude implement this feature based on the issue description or @claude how should I implement user authentication for this endpoint?. Claude answers in the same thread. Put your standards in a CLAUDE.md at the repository root (Day 2): Claude follows it on every run, and keeping it short saves tokens.
Who can trigger it. Two checks run before Claude starts. The person commenting must have write access to the repository, so a stranger cannot spend your money from a public issue. And bot accounts are rejected unless you list them in allowed_bots, which stops bots from triggering each other in a loop.
15Automatic review of every pull request
What and why. A review at the moment a PR opens catches the obvious problems before a teammate spends time on it. Here Day 10 pays off: the review logic comes from the official code-review plugin, installed inside the workflow. How:
name: Code Review on: pull_request: types: [opened, synchronize, ready_for_review, reopened] jobs: review: runs-on: ubuntu-latest permissions: contents: read pull-requests: read issues: read id-token: write steps: - uses: actions/checkout@v6 with: fetch-depth: 1 - uses: anthropics/claude-code-action@v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} plugin_marketplaces: "https://github.com/anthropics/claude-code.git" plugins: "code-review@claude-code-plugins" prompt: "/code-review:code-review --comment ${{ github.repository }}/pull/${{ github.event.pull_request.number }}" claude_args: '--allowedTools "mcp__github_inline_comment__create_inline_comment"'
plugin@marketplace-name. The marketplace name comes from the marketplace's own file, not its URL./plugin-name:skill-name form from Day 10.--allowedTools names it here.Claude skips draft and closed pull requests, ones it judges not to need review (automated or trivial) and ones it already commented on. On public repositories GitHub withholds secrets from fork pull requests, so this review runs only for branches in the same repository. If you want automatic reviews without maintaining a workflow, Anthropic also offers a separate Code Review feature.
16Run on a schedule
What and why. A cron trigger lets Claude do a recurring job: a morning summary, a weekly dependency check, a stale-issue sweep. How: give the workflow a prompt and a schedule. For a plain-text prompt Claude has no shell or GitHub access until you grant the tools it needs, so name them:
name: Daily Report on: schedule: - cron: "0 9 * * *" # 09:00 UTC every day jobs: report: runs-on: ubuntu-latest permissions: contents: read issues: read id-token: write steps: - uses: anthropics/claude-code-action@v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} prompt: "Generate a summary of yesterday's commits and open issues" claude_args: | --model claude-sonnet-5-5 --max-turns 8 --allowedTools "mcp__github__list_commits,mcp__github__list_issues"
Three details to know. GitHub runs scheduled workflows only from the default branch. In public repositories GitHub turns the schedule off after 60 days without repository activity. And a scheduled run is attributed to the person who last changed the cron line, so if that is a bot, add it to allowed_bots. In this example the report goes to the workflow run log, which is the default. Cron times are in UTC: 09:00 UTC is 14:30 in India.
17Costs and guardrails in CI
What. Every automated run spends two things: GitHub Actions minutes on GitHub's machines, and tokens on the model (or your subscription, if you authenticate with an OAuth token). Why it matters. A loop or a very busy repository can multiply both quietly. How to keep control:
--max-turns in claude_args, add a job timeout-minutes, and use GitHub concurrency controls so a burst of pushes does not start ten reviews at once.@claude requests and issue templates mean fewer turns. A short CLAUDE.md is read on every run.permissions: it needs. The review job above is read-only apart from comments.18Safety: the rules for unattended code
What and why. A chat session is supervised by you. A headless run or a workflow is not, so the safety work moves into the setup. The list below is the habits that matter most.
Bash(git diff *) to bare Bash. Add tools only after you see what was denied.19In VS Code
What and why. Headless mode and Actions are terminal and GitHub features, not extension screens, but you still do most of the work from VS Code. The Claude panel is for conversation. The pieces below are what you use around it.
Ctrl+`) and run claude -p there. Pipes and redirects work the same in PowerShell, bash and zsh..github/workflows/claude.yml from the Explorer with New File. YAML is indentation sensitive, so use spaces, and let VS Code show you the indent guides. Ask the Claude panel to check your YAML or explain a failed run./install-github-app in a Claude session inside the repository. In the panel or the terminal, it needs the GitHub CLI signed in first.claude -p command in the terminal before putting it in a workflow. If it works there with your --allowedTools list, it will behave the same in CI. Add --bare to match the clean CI environment.20When it does not work
@claude as a whole word (not /claude or @claude-bot), and the commenter has write access.GITHUB_TOKEN. Remove a github_token: ${{ secrets.GITHUB_TOKEN }} line so Claude pushes as the GitHub App, and check your CI triggers include push or pull_request.claude first. Bare mode ignores your login, so it needs ANTHROPIC_API_KEY.--allowedTools, or read the permission_denials in the output.--comment and the claude_args line with the inline-comment tool. Without them the findings stay in the run log.--output-format json, read the result field, and check structured_output exists. Do not parse the plain-text answer.--max-turns and a CI timeout. Background tasks make a headless run wait, up to 10 idle minutes by default.21Cheat sheet: I want this, so use that
claude -p "question"git diff main | claude -p "..."--permission-mode acceptEdits or --allowedTools "Read,Edit"--allowedTools "Bash(git commit *)"--permission-prompts none with --permission-mode dontAsk or auto--output-format json, read .result--json-schema, read .structured_output--bare plus ANTHROPIC_API_KEY--continue, or --resume <session_id>--max-budget-usd and --max-turnsclaude -p "/skill-name"/install-github-app (needs gh auth login)code-review plugin workflow with --commenton: schedule plus a prompt in the workflow22Questions beginners ask
--bare runs and CI you set ANTHROPIC_API_KEY (a key from the Claude Console), or in GitHub a subscription token made with claude setup-token.claude -p is the command-line form. The Python and TypeScript SDK packages give you more control inside a program, such as approval callbacks and structured message objects.--max-turns, job timeouts and concurrency limits, and watch your first week of runs before scaling up.claude -p command in a script and run it from cron on Linux and macOS, or from Task Scheduler on Windows. Use --bare, a budget cap, and a key from an environment variable.23Try this today
claude -p "summarize this repository in 5 bullets" in a project and check the exit code.git diff main into claude -p and ask for the root cause or typos.--output-format json and print only the result and total_cost_usd.--allowedTools list and confirm it cannot do anything else./install-github-app, merge the setup pull request and comment @claude on an issue.You now have the full ladder: talk to Claude Code, extend it, package it, and let it work on its own, with limits at every step.