# Set Up GitHub Actions CI for a Flask App

Yes. An agent reads your Flask repo, adds a GitHub Actions workflow that installs the pins, compiles translations and runs the tests, and fixes it until green.

[Run this on Rush](https://cloud.getrush.ai/run/add-github-actions-ci/flask/), in a Project that has your repository.

## What the agent does

The agent clones the repository into its own cloud sandbox and reads how the app is installed and tested before it writes a line of YAML. In a Flask app like miguelgrinberg/microblog there is no `.github` directory at all. `requirements.txt` pins everything: Flask 3.0.0, Flask-SQLAlchemy 3.1.1, SQLAlchemy 2.0.23, Flask-Migrate 4.0.5, flask-babel 4.0.0, gunicorn 21.2.0, psycopg2-binary 2.9.9, redis 5.0.1, rq 1.15.1 and elasticsearch 8.11.0. `.flaskenv` sets `FLASK_APP=microblog.py`. The only test module is `tests.py`, a `unittest.TestCase` named `UserModelCase` with four tests, and it ends in `unittest.main(verbosity=2)`, so `python -m unittest -v tests` is the command. There is no `.python-version`, and the `Dockerfile` builds from `python:slim` without a tag, so no file names a Python version.

It then works out what the tests need from the machine. `tests.py` defines a `TestConfig` that sets `SQLALCHEMY_DATABASE_URI` to `sqlite://` and `ELASTICSEARCH_URL` to `None`, so the suite needs neither PostgreSQL nor Elasticsearch. `app/__init__.py` creates a Redis connection from `REDIS_URL`, which defaults to `redis://` in `config.py`, but `Redis.from_url` opens no socket until a command is sent and no test enqueues work, so the workflow needs no Redis service either. `config.py` falls back to a SQLite file, `app.db`, when `DATABASE_URL` is unset, which means `flask db upgrade` can run the Alembic migrations in `migrations/versions` against a throwaway file as a check that they still apply.

With that read, it writes `.github/workflows/ci.yml`: on `push` to `main` and on `pull_request`, one job on `ubuntu-latest` that checks out the code, sets up Python with `actions/setup-python` and `cache: pip` keyed on `requirements.txt`, runs `pip install -r requirements.txt`, then `flask translate compile` (the step the `Dockerfile` also runs, because `app/cli.py` shells out to `pybabel compile -d app/translations` and a broken `.po` file should fail here, not at deploy), then `flask db upgrade` against the SQLite fallback, then `python -m unittest -v tests`. It pins the Python version in the workflow to the one it installed in the sandbox and says in the pull request why it chose that one, since the repository itself does not pick.

Before opening the pull request it runs every step itself in the sandbox, in the same order the workflow will, and reads the output. If `pip install` fails on a pinned wheel for the chosen interpreter it changes the version rather than loosening a pin, because the pins are the app's contract. It pushes the branch, waits for the workflow to run on GitHub, and reads the run's log through the GitHub App. A red run means it reads the failing step, fixes the workflow, pushes again and waits again; it does not finish on a red check.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Set up GitHub Actions CI for this Flask app. Read requirements.txt, .flaskenv, config.py and tests.py to learn how it installs and tests. Add .github/workflows/ci.yml that runs on push to main and on pull requests: set up Python with pip caching keyed on requirements.txt, install the pinned requirements, run `flask translate compile`, apply the migrations with `flask db upgrade` against the SQLite fallback, and run `python -m unittest -v tests`. Add no service containers the tests do not need, and change no application code or pins. Run every step here first, open a pull request, then wait for the workflow run on the pull request and fix the workflow until it is green.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request that adds one file, `.github/workflows/ci.yml`, with a green check from the workflow it added. The description lists the steps in order, names the Python version it pinned and why, and quotes the lines from `tests.py` and `config.py` that justify running without PostgreSQL, Elasticsearch or Redis. Nothing under `app/`, `migrations/` or `requirements.txt` changes.

A reviewer opens the workflow run on the pull request and reads the test step: four tests from `UserModelCase`, each named, with `OK` at the end, because the command uses `-v`. They check that the `flask translate compile` step compiled the catalogs under `app/translations` and that `flask db upgrade` walked every revision in `migrations/versions`. The session's transcript holds the same commands run in the sandbox first, with their output, so the reviewer can compare the two.

If a step cannot be made to pass without touching application code, the agent does not touch it. It says so in the pull request and in its final answer, with the output of the failing step, and leaves that step out of the workflow or marks it as allowed to fail, whichever the prompt asked for.

## Run it on every repository with no .github directory

A script on your server lists the organization's repositories through `GET /o/{org}/github`, which returns every repository your Rush GitHub App installation can reach, checks each for a `.github/workflows` directory, and sends one `POST /o/{org}/sessions` per repository that has none, with that repository as the `resources` URL. Each run works in its own sandbox and opens its own pull request, so ten Flask services get ten workflows and ten reviews, not one sweeping change.

Give the API key a `webhook_url` and Rush POSTs `run.completed` to it when each turn finishes, signed per Standard Webhooks, with the `session_url` to read the pull request from. A `run.failed` event names the run that needs a person.

## Why run it on Rush?

Writing CI for an app you did not write means guessing what the tests need until the workflow goes green, and each guess costs a push and a wait. The agent does the guessing in its own sandbox with the repository in front of it: it installs the pins, runs the suite, compiles the translations and applies the migrations before the first push, so the pull request usually arrives with one workflow run, not twelve.

The result carries its own proof. The pull request's check is the workflow the agent wrote, running on GitHub's machines, and the session keeps every command the agent ran first with its output. A reviewer compares the two and merges; nothing has to be taken on faith, and their own laptop never installed anything.

## Frequently asked questions

### Will it change my existing workflow files?

In a repository with no `.github` directory there is nothing to change. Where workflows exist, it adds a new file beside them rather than editing one, unless the prompt names the file to edit, and the pull request lists every file it touched.

### The tests import the app, which creates Redis and Elasticsearch clients. Why no service containers?

Because the clients connect lazily. `TestConfig` in `tests.py` sets `ELASTICSEARCH_URL` to `None`, so `create_app` never builds an Elasticsearch client, and `Redis.from_url` opens no connection until a command runs, which no test does. The agent reads that before deciding, and says so in the pull request. If you later add tests that enqueue RQ jobs, a `redis` service block is the one-line change.

### Which Python version does it use?

The repository does not say: there is no `.python-version`, and the `Dockerfile` uses `python:slim` with no tag. The agent pins the version it successfully installed the requirements under in the sandbox, names it in the pull request, and leaves it to you to change if production differs.

### Does it need my production secrets?

No. `config.py` has defaults for everything the tests touch: `SECRET_KEY` falls back to a placeholder, the database to a SQLite file, and mail is never sent. The workflow sets no secrets, and the agent never sees yours.

### Does it wait for the workflow to pass before it finishes?

Yes. After pushing it reads the workflow run on the pull request through the GitHub App, and if the run is red it reads the log, fixes the workflow and pushes again. A run it cannot make green without changing application code ends with that step's output in its final answer rather than a merged lie.
