Headless Claude Code: 5 Things I Run From My GitHub Actions
- Claude Code -p runs headless in CI with zero terminal
- Blog generation, daily audit, PR triage, release notes, README sync all on cron
- Each run costs 0.10 to 0.60 EUR depending on context size
- Pin the CLI version and cache state or you will burn tokens
I run five jobs from GitHub Actions that used to sit on my laptop. No terminal open, no me watching a progress bar. Claude Code in headless mode does the work on a schedule and opens a pull request when it has something to show me. Here is exactly what runs, what it costs, and the parts that broke before they worked.
Why headless Claude Code beats a local terminal
Most people use Claude Code interactively. You open a terminal, type a request, watch it edit files, approve a diff. That is fine for one-off work. It does not scale to anything that needs to happen every day at 6am while I am asleep.
The trick is the `-p` flag. It runs Claude Code in print mode: you pass a prompt, it does the work, it prints the result, and it exits. No interactive session, no approval prompts. That makes it a normal command line tool, which means GitHub Actions can run it the same way it runs a test suite.
A minimal job looks like this. You install the CLI, set your API key as a secret, and call it with a prompt.
- run: npm install -g @anthropic-ai/claude-code
- run: claude -p "Read CHANGES.md and write release notes to RELEASE.md"
env:
CLAUDE_KEY: ${{ secrets.CLAUDE_KEY }}
That is the whole pattern. Everything else is variations on it. The job clones my repo, Claude reads files and writes files, and a later step commits the changes or opens a pull request.
The reason this matters is consistency. When work depends on me remembering to run a script, it happens maybe three times a week. When it runs on a cron trigger, it happens 365 days a year without a single missed day. My daily audit has run 90 times in a row now. I would have skipped at least 40 of those by hand.
Costs are lower than people expect. A typical headless run uses between 15,000 and 80,000 tokens depending on how much context it reads. That lands between 0.10 and 0.60 EUR per run. My five jobs combined cost under 12 EUR a month. The GitHub Actions minutes are free for the volume I use.
The one mental shift: headless Claude cannot ask you questions. If it gets confused, it guesses and moves on. So your prompts have to be more precise than they would be in a chat. I write them like specs, not like requests. If you want the full setup behind all of this, the Claude Blueprint walks through the foundation.
Blog generation and the daily audit
Two of my five jobs touch this site directly. The first generates blog drafts. The second audits what is already published.
The generation job runs three times a week. It pulls a topic from a queue file, reads three existing articles for voice reference, and writes a full draft following a structure spec. The output goes into a pull request, never straight to publish. I read every draft before it goes live, because headless mode will confidently produce a paragraph that is technically wrong if the prompt leaves room.
The prompt for this job is long, around 600 words. It includes the word count target, the section structure, the banned words, and three example openings. The more I front-load into the prompt, the less I fix afterward. When I started, I gave it two sentences of instruction and spent 20 minutes editing each draft. Now the prompt does the heavy lifting and I spend 4 minutes.
The daily audit is the job I am proudest of. Every morning at 7am it reads my last 10 published articles and checks them against a rule list: broken internal links, missing affiliate disclosures, headings out of order, word counts that drifted out of range. It writes a report to a markdown file and, if it finds something broken, opens an issue with the specific fix.
In 90 days it has caught 31 real problems. Eleven were dead internal links where I renamed a handle and forgot to update the references. Nine were articles that drifted under my word floor. The rest were formatting drift. None of those would I have found by hand, because who re-reads their own archive every morning.
The audit costs more than the other jobs because it reads more files. A full run is around 70,000 tokens, so about 0.50 EUR. Over a month that is 15 EUR for a job that has saved me from publishing broken links 31 times. Easy trade.
One gotcha: the audit used to read every article in the repo, which pushed token use past 200,000 and the cost past 1.50 EUR per run. I capped it at the 10 most recent files by modified date. Same value, a fifth of the cost. If a job feels expensive, the answer is almost always "you are feeding it too much context."
PR triage and release notes
The third and fourth jobs handle the boring parts of maintaining the repo itself.
PR triage runs whenever a pull request opens. Most of my pull requests come from the blog generation job, but I also get the occasional manual one. The triage job reads the diff, checks it against my rules, and posts a comment summarizing what changed and whether anything looks off. For a blog draft it confirms the word count is in range and the internal links resolve. For a code change it flags anything that touches secrets or the publish flow.
This is not code review in any deep sense. It is a first-pass filter that catches the obvious stuff so I do not have to. The comment shows up within 90 seconds of the pull request opening. By the time I look at it, the summary is already there.
on:
pull_request:
types: [opened, synchronize]
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install -g @anthropic-ai/claude-code
- run: claude -p "Review the diff in this PR against the rules in RULES.md. Post a 5-line summary."
env:
CLAUDE_KEY: ${{ secrets.CLAUDE_KEY }}
Release notes are the fourth job. Every time I tag a version, this job reads the commit messages since the last tag, groups them by type, and writes human-readable release notes. My commit messages are terse and ugly. The release notes that come out are clean and grouped under headings like "Fixes" and "New." It turns "fix typo in audit prompt lol" into a proper changelog entry without me thinking about it.
The cost here is tiny, around 0.10 EUR per run, because commit messages are short. This job has written 14 release entries. I have edited exactly two of them. The rest shipped as written.
The biggest gotcha across both jobs is permissions. GitHub Actions needs explicit write permission to post comments and commit. I lost an hour to a job that ran perfectly but silently failed to post because the workflow had read-only token scope. Add `permissions: pull-requests: write` and `contents: write` at the job level. If you schedule social posts off the back of a release, Buffer takes a webhook and handles the queue.
README sync and the gotchas that cost me money
The fifth job keeps my README honest. Documentation drifts. You change a flag, you add a script, you rename a folder, and the README quietly becomes a lie. This job reads the actual project structure and the current scripts, then rewrites the relevant README sections to match reality.
It runs weekly. It opens a pull request with the diff so I can see exactly what changed before it lands. About one week in three it has a real change. The other weeks it opens nothing, which is the correct behavior. A job that does nothing when there is nothing to do is a good job.
Now the gotchas, because these cost me real tokens before I fixed them.
First, pin the CLI version. I ran `npm install -g @anthropic-ai/claude-code` with no version, and one morning a new release changed the default model and my token use jumped 40 percent overnight. Pin it: `@anthropic-ai/claude-code@2.0.1`. Upgrade on purpose, not by accident.
Second, cache nothing you do not understand. I tried caching Claude's internal state between runs to save context reads. It saved tokens but produced stale output because the cache held a version of a file that had changed. For these jobs, fresh reads every time are worth the extra 0.05 EUR. Cache the npm install, not the reasoning.
Third, set a hard timeout. A headless job that gets stuck in a loop will keep calling the API until something stops it. I add `timeout-minutes: 10` to every job. One runaway run cost me 3 EUR before the timeout existed. Now the worst case is bounded.
Fourth, never let a headless job publish without a human gate. Every one of my five jobs either opens a pull request or writes a draft. None of them push to production directly. Headless Claude is good, not perfect, and the cost of a bad article going live is much higher than the cost of me reading a draft for 4 minutes.
Fifth, log the token count. Claude Code prints usage at the end of a run. Pipe it to a file and check it weekly. That is how I caught the version bump that doubled my costs. Visibility is the whole game when the work happens while you sleep.
Bottom Line
Headless Claude Code turned five chores I used to skip into five jobs that run themselves. The blog drafter, the daily audit, the PR triage, the release notes, and the README sync cost me under 12 EUR a month combined and save me hours I was not spending anyway, because half of them I simply never did by hand.
The pattern is the same every time: install the CLI, pass a precise prompt with `-p`, write the output to a file, open a pull request, keep a human gate before anything ships. Pin your version, set a timeout, log your tokens, and feed each job the smallest amount of context that still does the work.
If you want the full foundation behind how I run Claude as infrastructure rather than a chat window, the Claude Blueprint is where I keep the setup. Start with one job. The daily audit is the easiest win and the one most likely to catch something embarrassing before your readers do.
This article contains affiliate links. If you sign up through them, I may earn a small commission at no extra cost to you. (Ad)
Back to all articles