Cooking Calm into Continuous Integration: Claude Headless Reviews and Coverage Served Fresh
When I cook dinner, I do not just throw ingredients into a pan. I set things out first, the knife sharpened, the pot already warming, the vegetables washed and ready. That small bit of preparation changes the entire experience. Cooking feels calmer, and the results come out more consistent.
I like my repositories to feel the same way. Without preparation, reviews get chaotic, tests slip through, and discussions stall on whether coverage went up or down. With the right setup, everything flows. Automation takes care of the routine checks, and I can focus on the real conversation.
The recipe is simple:
Claude headless runs on every pull request, generating a concise Markdown review.
A coverage report is converted to Markdown and posted back into the same thread.
All the information you need, potential risks, missing tests, overall coverage, appears right inside the pull request. No dashboards, no external tools, just a quiet workflow that does its job.
What is Claude headless
Normally, you interact with Claude through a chat interface. Headless mode is different: it lets you run Claude directly from the command line or inside automation, without any back and forth. You give it a prompt and the necessary context such as a git diff or a codebase, and it prints out the result.
This is its real superpower. Because it runs in a fully automated way, you can plug Claude into Continuous Integration, nightly jobs, or even scheduled tasks that run across multiple repositories. It does not just answer questions, it becomes part of your build process.
Some examples of what becomes possible:
Automated code reviews that catch bugs and highlight missing tests
Refactoring suggestions written as Markdown design notes
Security checks for risky patterns in authentication or cryptography
Documentation updates that stay in sync with code changes
Release notes generated automatically from commit history
Headless mode does not just answer developers, it works with them, quietly, in the background.
The recipe
I have put together a complete example here: github.com/gbarbalinardo/headless-claude-ci
The repo includes:
a small Python project with one utility function and a test
a GitHub Actions workflow that runs both Claude and coverage
a Python script that turns coverage.xml into a Markdown table
It is deliberately small so you can read it in one sitting, but it shows the full pattern end to end.
The workflow file .github/workflows/ci.yml does the heavy lifting. Here is a simplified view:
On every pull request, it:
runs the test suite with pytest and collects coverage
generates a Markdown summary of coverage
calls Claude in headless mode on the diff
combines both outputs and posts a single PR comment
jobs:
review-and-coverage:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install pytest pytest-cov coverage
- run: pytest --cov=src --cov-report=xml:coverage.xml
- run: python scripts/coverage_to_md.py coverage.xml > coverage.md
- run: npm install -g @anthropic-ai/claude-code
- name: Run Claude headless review
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
BASE_SHA=$(git merge-base origin/${{ github.base_ref }} HEAD)
git diff $BASE_SHA...HEAD > pr.diff
claude -p "You are a precise code reviewer. Summarize pr.diff in Markdown, noting potential bugs, risks, and missing tests. Keep it under 300 words." \
--allowedTools "Read,Grep,GlobTool" \
--output-format text \
< pr.diff > claude_review.md
- name: Post combined PR comment
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const review = fs.readFileSync('claude_review.md','utf8');
const cov = fs.readFileSync('coverage.md','utf8');
const body = `## Claude review\n\n${review}\n\n---\n\n${cov}`;
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});
The coverage script
The Markdown coverage summary comes from a tiny script in scripts/coverage_to_md.py:
import sys, xml.etree.ElementTree as ET
root = ET.parse(sys.argv[1]).getroot()
total = float(root.attrib.get("line-rate", 0)) * 100.0
rows = []
for cls in root.findall(".//class"):
fn = cls.attrib.get("filename", "unknown")
rate = float(cls.attrib.get("line-rate", 0)) * 100.0
rows.append((rate, fn))
rows.sort()
print("# Test coverage\n")
print(f"Total line coverage: **{total:.1f}%**\n")
print("| File | Coverage |")
print("|---|---|")
for rate, fn in rows[:5]:
print(f"| {fn} | {rate:.1f}% |")
When the workflow runs, this generates a Markdown table with the overall coverage percentage and the least covered files. That Markdown is then appended to Claude’s review and posted as a single PR comment.
Why it matters
By keeping everything in Markdown, the workflow stays simple. Developers do not need to click away to external sites or parse raw XML. They see coverage, file by file, right where they already work. Claude’s review appears alongside the coverage, giving every pull request the same baseline: are the tests thorough, and are there risks worth flagging?
It does not replace human review, but it sets the table. Just as in cooking, when the preparation is done right, the real work feels lighter and more focused.
Closing thought
Just as cooking feels calmer when the prep work is done, development feels lighter when automation handles the basics. Claude headless brings intelligence into that loop, reviews that never tire, coverage summaries that always appear, and the potential for much more: automated documentation, release notes, security scans, even large scale refactors.
With this setup, your repository feels like a well prepared kitchen: quiet, steady, and always ready for the next dish.



