Run an AI coding agent headless in CI

Install the oharness package and run oharness-headless "<task>": it streams one JSON event per line to stdout and exits 1 if the run failed. Nobody can answer a permission prompt in CI, so anything that would ask is denied — pre-authorise the work with rules or --permission-mode.

Updated

Cette page est rédigée en anglais. Les termes qu'elle explique sont ceux que l'on cherche en anglais, et un terme technique traduit est un terme différent.

The command

oharness-headless -m deepseek/deepseek-v4-flash "run the tests and fix what fails"

The prompt can come from an argument or from stdin (echo "task" | oharness-headless). -C <dir> sets the working directory, -r <id> continues a session, and --permission-mode is one of default, acceptEdits, plan, auto or bypassPermissions.

What comes out

stdout is JSONL and nothing else, so it pipes into jq; diagnostics go to stderr. The stream ends with a done event carrying the stop reason, usage, cost and turn count:

{"type":"session","id":"20260808_...","model":"deepseek-v4-flash","cwd":"/repo"}
{"type":"tool_start","toolCallId":"c1","toolName":"grep","title":"grep(build)","input":{...}}
{"type":"text_delta","text":"The build entry point is "}
{"type":"done","stopReason":"end_turn","usage":{...},"costUsd":0.004,"turns":2}

Permissions without a human

A run that silently approved everything would be more dangerous unattended than attended, so headless does the opposite: a tool that would prompt is denied and reported as a permission_result event. Allow what the job needs in the project's .oharness/settings.json, where it is reviewable in the repository:

{
  "allow": ["Bash(npm test*)", "Bash(npm run lint*)", "edit", "write"],
  "deny":  ["Bash(git push*)", "Bash(rm -rf *)", "write(.env*)"]
}

deny wins over every mode, including bypassPermissions. Questions the agent would put to a user return at once with an instruction to choose a sensible default and say so, and still appear in the stream as question_request and question_result.

Credentials in CI

Use the provider's key variable as a CI secret — DEEPSEEK_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY and so on. On the hosted plan, a key created on the site goes in OHARNESS_API_KEY, and OHARNESS_TEAM bills the run to a team. The update check is off automatically when CI is set.

A GitHub Actions step

- run: npm install -g oharness
- run: oharness-headless --permission-mode acceptEdits "run npm test and fix failures" > run.jsonl
  env:
    DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
- run: jq -r 'select(.type=="done") | .stopReason' run.jsonl

Common questions

How do I run a coding agent non-interactively?

With OHarness, run oharness-headless "<task>". It prints the agent's events as JSONL on stdout, writes diagnostics to stderr, and exits with code 1 if the run failed.

What happens to permission prompts in headless mode?

They are denied and reported as permission_result events, because nobody is present to answer. Allow the tools the job needs with permission rules in .oharness/settings.json, or choose a --permission-mode.

Which models can run headless?

Any model OHarness can reach: Anthropic, OpenAI, Gemini, DeepSeek, any OpenAI-compatible endpoint, or a local server reachable from the runner. Pick it with -m provider/model.

Related