# Add Health Checks to a Wagtail Site

Yes. An agent adds django-health-check to a Wagtail site with database and cache checks, a /health/ route, a Django test and a Docker HEALTHCHECK, and opens a PR.

[Run this on Rush](https://cloud.getrush.ai/run/add-health-checks/wagtail/), in a Project that has your repository.

## What the agent does

The agent reads how the site is run before it adds a route. In a site shaped like wagtail/bakerydemo, `requirements/base.txt` pins Django to the 6.0 line and Wagtail to 8.0, with WhiteNoise and `dj-database-url`; `requirements/production.txt` adds uWSGI, `psycopg`, `django-redis` and `django-basic-auth-ip-whitelist`; and `runtime.txt` and the `Dockerfile` both fix Python 3.12. `bakerydemo/settings/base.py` reads `DATABASE_URL` and falls back to SQLite, `production.py` builds a Redis cache from `REDIS_URL` when it is set and reads `ALLOWED_HOSTS` from `DJANGO_ALLOWED_HOSTS`, and `test.py` swaps in a local-memory cache. `docker-compose.yml` runs `postgres:18.1`, `redis:8.4` and the app on port 8000; `docker-entrypoint.sh` waits for Postgres with a `psql` loop and migrates; and the image's `CMD` is `uwsgi /code/etc/uwsgi.ini` with two workers and four threads on `$(PORT)`, which the `Dockerfile` sets to 8000.

`bakerydemo/urls.py` is where the route goes. It mounts the Django admin, the Wagtail admin, documents, image serving, search, `sitemap.xml`, the API routers and the debug toolbar, then Wagtail's page URLs last. `AGENTS.md` asks for tests under `bakerydemo/<app>/tests/`, run with `DJANGO_SETTINGS_MODULE=bakerydemo.settings.test`, and `CONTRIBUTING.md` asks that AI assistance be disclosed in the pull request with the steps taken to verify the code, which the agent does.

The change uses `django-health-check`, whose current 4.10 release requires Django 5.2 or newer. The agent adds it to `requirements/base.txt` with a bound on the 4 line, adds `health_check`, `health_check.db` and `health_check.cache` to `INSTALLED_APPS` in `base.py`, and appends `health_check.contrib.redis` in `production.py` inside the existing `if REDIS_URL:` block, next to the cache it configures, with `REDIS_URL` as its target. In `urls.py` the route `path("health/", include("health_check.urls"))` is placed before the Wagtail catch-all so a page with the slug `health` can never shadow it. The view answers HTML by default and JSON with `?format=json`, which is what a load balancer or an uptime probe reads.

The test follows the repository's own pattern. `bakerydemo/base/tests/test_health.py` sits beside `test_home_page.py`, requests `/health/?format=json`, asserts a 200 and that the `DatabaseBackend` and `CacheBackend` entries read `working`, and then patches the cache to raise and asserts a 500 with the failing entry named, so the route proves something rather than always answering OK. For the container the agent adds a `HEALTHCHECK` to the `Dockerfile` that runs a short Python `urllib` request against `http://localhost:8000/health/?format=json`, since the slim image carries no `curl`. It runs `uv pip install -r requirements/development.txt`, `./manage.py check`, `./manage.py makemigrations --check --noinput`, `./manage.py test` under the test settings and `make lint-server`, the steps `.github/workflows/ci.yml` runs, and brings up `docker compose` to read the route through uWSGI once.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Add health checks to this Wagtail site. Read requirements/base.txt, requirements/production.txt, bakerydemo/settings/base.py, bakerydemo/settings/production.py, bakerydemo/settings/test.py, bakerydemo/urls.py, Dockerfile, docker-compose.yml, docker-entrypoint.sh, AGENTS.md and .github/workflows/ci.yml first. Add django-health-check with database and cache checks, add the Redis check only in the production settings where REDIS_URL is configured, mount it at /health/ before the Wagtail catch-all, add a Django test under bakerydemo/base/tests that reads /health/?format=json and covers a failing cache, add a Dockerfile HEALTHCHECK that uses Python rather than curl, run manage.py check, makemigrations --check, the test suite under bakerydemo.settings.test and make lint-server, confirm the route through docker compose, and open a pull request that discloses the agent's work and how it was verified.",
  "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 on its own branch. The diff touches `requirements/base.txt` for the package, `bakerydemo/settings/base.py` and `bakerydemo/settings/production.py` for the apps and the Redis target, `bakerydemo/urls.py` for the route, `bakerydemo/base/tests/test_health.py` for the test and the `Dockerfile` for the `HEALTHCHECK`. No migration is created, because the package's checks keep no models of their own, and `makemigrations --check` proves it. The description lists the checks by name, shows the JSON the route answers, and discloses the AI assistance and the verification steps as `CONTRIBUTING.md` asks.

The checks are the proof. The `CI` workflow runs on the pull request: `lint-server` runs Ruff, curlylint and djhtml, and `test-server` runs `manage.py check`, `makemigrations --check` and `manage.py test` under the test settings with uv. The session's trajectory at `GET /o/{org}/sessions/{id}/messages` keeps every one of those commands with its output, and the `docker compose` request that shows the route answering through uWSGI, so a reviewer reads the evidence rather than a claim.

Before merging, a reviewer checks that `/health/` is reachable by the probe that needs it and not indexed or linked anywhere public, that the Redis check is added only where `REDIS_URL` exists so a deployment without Redis does not report itself down, and that the `HEALTHCHECK` interval suits the platform, since Heroku ignores it and Compose honours it. Nothing reaches main until the pull request merges.

## Run it on every new Wagtail site

Start one run per site that has no health route from your own server: `POST /o/{org}/sessions` with this prompt and that repository in `resources`, one call each. `GET /o/{org}/github` lists every repository the Rush GitHub App installation can reach, so a script can find the ones with `wagtail` in a requirements file and no `health_check` in their settings and start a run for each. A `request_id` built from the repository name makes a retried call return the run it already started.

Give the key a `webhook_url` and each run POSTs `run.completed` with its `session_url` when the turn finishes, so the pull request links collect on your side without polling. A key has at most `max_concurrent` runs in progress at once, 4 by default, and a further start is `429`, so a sweep over many sites should feed them in as earlier runs finish.

## Why run it on Rush?

A health route is small, but proving it means running the site the way production runs it: Postgres and Redis up, uWSGI in front, the route read through the container, and then a failing backend to see the status flip. On a laptop that is a Compose stack and a `uv` environment for a ten-line change. On Rush the agent does that in its own sandbox with Docker, runs the Django suite under the test settings and the lint target, and pushes only when the CI steps pass locally.

The pull request is also the disclosure the project asks for. It names the checks, shows the JSON, says what the agent ran, and the trajectory keeps each command and its output for anyone who wants to look. A sweep over several sites becomes several sandboxes and several pull requests at once, each with its own record, rather than an afternoon per site.

## Frequently asked questions

### What does the route check?

The database, through `health_check.db`, which writes and deletes a row; the cache, through `health_check.cache`; and in production, Redis at `REDIS_URL` through `health_check.contrib.redis`. Each check reports `working` or an error, and the HTTP status is 200 only when all pass. The test covers the passing case and a failing cache.

### Why is the Redis check only in production.py?

Because that is the only settings module that configures Redis: `production.py` builds the cache from `REDIS_URL` when it is set, and `test.py` uses a local-memory cache. Adding the Redis check in `base.py` would make the test suite and a local SQLite setup report themselves unhealthy for a service they do not use.

### Does the health view need authentication?

No, and it is not given any, because a load balancer or uptime probe cannot sign in. The route exposes the names of the checks and their status, not credentials or configuration. If the site runs behind `django-basic-auth-ip-whitelist` from `requirements/production.txt`, the reviewer decides whether the probe's address needs to be on the whitelist.

### Why use Python instead of curl in the HEALTHCHECK?

The image is built from `python:3.12-slim` and the `Dockerfile` installs only the runtime packages the site needs, which do not include `curl`. A one-line `python -c` with `urllib.request` is already available, and the check targets port 8000, which the `Dockerfile` sets as `PORT` and `etc/uwsgi.ini` binds.

### Will a page with the slug health break the route?

No. The route is added to `urlpatterns` before the `include(wagtail_urls)` entry that serves pages, so `/health/` resolves to the view first. The test requests the path and asserts a JSON response, which a Wagtail page would not return.
