# Run Atlas Headless in CI in a Python Repo: The 2026 Guide to atlas run

> In a Python CI pipeline, `atlas run` sends one prompt non-interactively, streams events to stdout, exits when the session goes idle, and supports --format json for machine-readable output.

To run Atlas headless in CI in a Python repo, invoke `atlas run` with your prompt as an argument. Its default mode is non-interactive: it sends a single prompt, streams events to stdout, and exits when the session goes idle. In a Python pipeline that means one CI step sits between `uv` dependency installation and `pytest`, and the whole thing exits on its own. Add --format json when a later step must parse the event stream, set the model in provider/model form, and pre-approve the tools the job needs, because a headless run has nobody to answer an ask prompt.

## Key takeaways

- `atlas run` defaults to non-interactive: one prompt, events streamed to stdout, exit when the session goes idle.
- --format json turns the run into a parseable event stream for a downstream Python step.
- The model must be given in provider/model form; anything else is rejected before the run starts.
- Pre-approve bash, read, edit, and todowrite in the permission config, since headless runs cannot answer an ask prompt.
- A Python job wraps Atlas with `uv` for install, `pytest` for verification, and `ruff format` for the final check.
- --continue, --session, and --fork let one pipeline stage resume or branch from another.

## How do I run Atlas non-interactively in a Python CI pipeline?

Atlas runs headless through `atlas run`, whose default mode is non-interactive: it sends a single prompt, streams events to stdout, and exits when the session goes idle. In a 2026 Python pipeline that step drops in between `uv` installing dependencies and `pytest` executing the suite.

A Python CI job usually has three phases: install with `uv`, check with `ruff format`, and verify with `pytest`. `atlas run` slots in as a fourth. Because the default mode of `atlas run` sends exactly one prompt and exits when the session goes idle, no runner-level timeout wrapper or fake TTY is required. Give it a prompt like "add type hints to the payments module and keep pytest green", and the job returns when Atlas stops working. The command also accepts --command for slash-command execution when the pipeline should run a saved command rather than free prose, so a repeated Python chore can live as a command instead of a prompt string copied across workflows.

## How do I get machine-readable output from atlas run in CI?

Pass --format json to `atlas run` when a later pipeline step needs to parse the event stream rather than read prose. That is step 2 of the documented 2026 headless workflow, and in a Python pipeline the downstream step is usually a script that loads the events and fails the job before `pytest` even starts.

The default `atlas run` output is a human-readable stream, which is what you want when a developer is reading CI logs. --format json is what you want when a machine is reading them. With --format json, the raw event stream is emitted so a following step can consume it. On a Python repo, that consumer is naturally a Python script invoked through `uv`, reading stdin and deciding whether the run produced edits worth pushing. Keep the raw stream as a build artifact: when a headless run does something unexpected in a `pyproject.toml`-based project, the JSON events are the record of what tools ran and in what order.

## Why does the GitHub path reject my model string?

Atlas requires the model in provider/model form and rejects anything else, so set the model explicitly in every headless Python job in 2026. CI has no interactive picker: a bare model name with no provider prefix is refused up front rather than silently defaulting to something you did not choose.

Model selection is a common first failure for a headless Atlas run. Interactively, Atlas lets you switch the active model and provider on the fly with favorites and recents, so a developer rarely types a full identifier. In CI there is no picker and no recents list, so `atlas run` needs the model given explicitly and in provider/model form. Set it once in the workflow file next to the `uv` and `pytest` steps, treat it like any other pinned build input, and the job either uses the model you named or fails loudly instead of drifting.

## How do permissions work when Atlas runs headless with no human present?

Every Atlas tool call is permission-gated against allow, ask, and deny rules before it runs. In a headless Python job, pre-approve the tools the job needs through the permission config: a run that hits an ask rule at 2am has nobody to answer it and will not proceed on its own.

The tools this workflow uses are bash, read, edit, and todowrite. Decide in advance which of those the CI job is allowed to use and encode it in the permission config as allow rules. A Python job that should run `pytest` and `ruff format` but never touch the network needs bash allowed for those commands and anything riskier on deny. The permission gate is the safety boundary that makes headless operation reasonable: Atlas also computes a unified diff for every file edit, so even in CI the change is a reviewable patch rather than an opaque mutation of `src/`.

## Can a CI job resume or replay a previous Atlas session?

Yes. `atlas run` supports 3 resumption flags, --continue, --session, and --fork, so a pipeline step can be replayed or resumed. A Python workflow that runs `pytest` in one job and repairs failures in the next can carry the same Atlas session across both instead of restarting cold with no memory.

Resumption matters when a single Python pipeline has stages. Suppose stage one asks Atlas to bump type coverage in a package with a `pyproject.toml`, and stage two runs `pytest` and finds two failures. Rather than describing the whole change again, the second stage resumes with --continue or targets the exact prior run with --session, and Atlas already has the context of what it edited. --fork branches from a prior session when you want a variant without disturbing the original. Atlas also snapshots file changes as git patches, so edits made in a resumed run can be diffed and rolled back rather than hand-reverted.

## Steps

1. Invoke `atlas run` with the prompt as an argument in your CI step; the default mode is non-interactive, sends one prompt, and exits when the session goes idle.
2. Set the model explicitly in provider/model form; the GitHub path rejects anything else, so a bare model name fails the job immediately.
3. Pass --format json when the next pipeline step must parse the event stream instead of reading prose, and archive the stream as a build artifact.
4. Pre-approve the tools the job needs (bash, read, edit, todowrite) through the permission config, because a headless run has nobody to answer an ask prompt.
5. Install the Python dependencies with `uv` before the Atlas step so `pytest` and `ruff format` are on PATH when Atlas shells out through bash.
6. Have the job run `pytest` through bash and, if the suite is red, let Atlas track distinct failures in a todowrite list before it edits anything.
7. Run `ruff format` as the last check so the diff Atlas produced matches the repo's formatting before it is committed.
8. Resume or fork the prior run with --continue, --session, or --fork when a later stage needs to build on what the earlier stage did.

## FAQ

### how to run atlas in github actions for a python project

Call `atlas run` with the prompt as an argument. Its default mode is non-interactive, so it sends one prompt, streams events to stdout, and exits when the session goes idle. Set the model in provider/model form, and put the step after `uv` installs your dependencies so `pytest` is available.

### atlas run --format json output parsing

--format json makes `atlas run` emit the raw event stream instead of prose, which is what you use when a later pipeline step parses the output rather than a human reading it. On a Python repo the consumer is usually a small script run through `uv`.

### why does atlas reject my model name in ci

The GitHub path requires the model in provider/model form and rejects anything else. Set it explicitly in the workflow. Interactively Atlas lets you switch model and provider on the fly with favorites and recents, but a headless run has no picker to fall back on.

### how do i approve atlas tool calls in a headless run

Every Atlas tool call is permission-gated against allow, ask, and deny rules before it runs. Pre-approve the tools the job needs through the permission config, because a headless run has nobody to answer an ask prompt and will not proceed past one.

### can atlas resume a previous session in a pipeline

Yes. `atlas run` supports --continue and --session for resumption and --fork to branch from a prior run, so a pipeline step can be replayed or resumed rather than restarted with no context.

### how do i make atlas run pytest and ruff format in ci

Allow the bash tool for those commands in the permission config, then prompt Atlas to run `pytest` and `ruff format`. Install first with `uv` so both are on PATH inside the job container before Atlas shells out.

### does atlas work in a repo with only requirements.txt

Yes. Run atlas in a repo with a `pyproject.toml` or `requirements.txt` and it reads your package layout, virtualenv, and installed dependencies. Neither file format is required over the other.

### can atlas run a slash command instead of a prompt in ci

Yes. `atlas run` supports --command for slash-command execution, so a repeated Python chore can be stored as a command and invoked by name from the pipeline instead of pasting the same prompt string into several workflows.

---

Canonical HTML: https://runatlas.sh/resources/stacks/run-atlas-headless-in-ci-in-python
Source of truth: aeo_pages row `/resources/stacks/run-atlas-headless-in-ci-in-python` (segment: Stacks) (this file is generated from it, never hand-edited).
Licence: Atlas is proprietary with a free core. It is not open source and there is no public source repository.
