What the agent does
A Grafana Cloud alert posting to a Rush webhook trigger starts the run, with the notification after the prompt as data. The agent reads the rule through the Grafana connection, reruns its query around startsAt, and pulls the Loki lines for the same window. In a FastAPI app served by uvicorn the access log already has what it needs: the Mealie meal planner configures it in mealie/core/logger/logconf.prod.json, where the access formatter prints the client address, the status code and the request line, and the detailed formatter prints [LEVEL|module|Lline] before each application message. A LogQL query for status 500 lines plus the traceback that follows them gives the agent the route and the module.
Mealie has no Prometheus exporter in pyproject.toml, so the alert rule is usually a Loki metric query over those access lines or a metric from the proxy in front of uvicorn, and both lead to mealie/routes. The meal plan API is mealie/routes/households/controller_mealplan.py: APIRouter(prefix="/households/mealplans") with GroupMealplanController, where get_all pages entries between start_date and end_date, create_one and update_one write a SavePlanEntry, get_todays_meals answers /today, and create_random_meal on /random picks a dish through _get_random_recipes_from_mealplan, which combines the household's PlanRules into one query filter with QueryFilterBuilder.combine_filters, pages with order_by="random", and raises not_found when nothing matches. The shopping API is controller_shopping_lists.py with two routers, /households/shopping/lists and /households/shopping/items: add_recipe_ingredients_to_list on POST /{item_id}/recipe, its .../recipe/{recipe_id}/delete counterpart, update_label_settings, and bulk item routes whose publish_list_item_events fans out one event per created, updated or deleted item.
The tests already cover these routes: tests/integration_tests/user_household_tests/test_group_mealplan.py, test_group_shopping_lists.py and test_group_shopping_list_items.py call the API through the session-scoped api_client from tests/conftest.py with a unique_user, build data with the shopping_lists fixture and the one that creates a random dish, and take their paths from tests/utils/api_routes. The agent adds a test in the matching file that sends the request the access line recorded, with the same date range, rule or item ids built through the fixtures, and asserts the status the alert saw.
Setup follows .github/workflows/test-backend.yml: Python 3.14 (the project requires >=3.14,<3.15), uv sync --group dev with UV_FROZEN=1 because [tool.uv] sets a five-day exclude-newer window that would otherwise re-resolve, and DB_ENGINE=sqlite for the default matrix entry; Postgres is a second entry with --extra pgsql. task py:test runs uv run pytest -n auto --dist loadfile, and pyproject.toml turns every unexpected warning into a failure. After the fix the agent runs the one file, then task py:test, uv run ruff format . --check, task py:lint and task py:mypy, the checks the workflow runs before pytest, apart from its Semgrep rules.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "A Grafana alert fired for this FastAPI service; its payload follows this prompt. Use the Grafana connection to read the alert rule, rerun its query around the firing time, and query Loki for the uvicorn access lines and the tracebacks in the same window. Find the cause in mealie/routes/households/controller_mealplan.py or controller_shopping_lists.py and the repositories and services they call. Install with uv sync --group dev using the committed lockfile on Python 3.14, add a pytest test in the matching file under tests/integration_tests/user_household_tests that sends the logged request through api_client and reproduces the failure, fix it, and run that file, then task py:test, uv run ruff format . --check, task py:lint and task py:mypy. Open a pull request that includes the LogQL and PromQL queries, the log lines behind the diagnosis, and the test output before and after the fix. If the evidence does not point at code, say what it points at instead.\n\nAlert payload:\n{payload}",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
],
"connections": [
"grafana"
]
}- 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.
- 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.
- 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 whose description shows the rule's query with the series that crossed its threshold, the LogQL query with the access lines and traceback it returned, and the new test failing and then passing under pytest. The diff is the test and the fix in one route controller, repository or service module.
A reviewer checks that the test's request matches the logged one, that a fix in create_random_meal still raises not_found when nothing matches the household's rules rather than returning an empty entry, that shopping list changes still publish one event per item, and that ruff and mypy stayed clean. The backend workflow then reruns both database entries on the pull request.
When the evidence points away from code, for example at the LDAP server the workflow starts or at a slow SQLite file under concurrent writes, the run says so with the queries and lines it found and opens no pull request.
Run it on every Grafana alert on the API
Add an automation to the Project with this prompt and a webhook trigger. In Grafana Cloud, create a webhook contact point at the trigger's URL with Authorization: Bearer <secret> and point the service's alert rules at it. The notification Grafana sends becomes the event text the agent reads under the prompt.
Turn off resolved notifications on that contact point and keep the notification policy's repeat interval longer than a run, since a delivery with no Idempotency-Key header always starts a new run. The automation's max_runs_per_hour is the limit for a rule that flaps.
The use case this job follows, with its automation and trigger: Fix a bug from an alert.
Why run it on Rush?
Chasing a FastAPI alert by hand means finding the access line, matching it to a traceback, rebuilding the request with the right household and entry ids, and running a suite that treats every warning as a failure. A Rush run does that in its own sandbox with the locked dependencies, and keeps each query and each pytest run with its output in the session.
The pull request shows the LogQL, the PromQL and the failing-then-passing test, so a reviewer checks the reasoning rather than repeating it. Alerts on the meal plan and shopping routes run in parallel, each in its own sandbox, and none reaches the production database.
Frequently asked questions
My FastAPI app logs JSON, not uvicorn's access format. Does that matter?
No. The agent reads whatever the Loki lines contain; JSON lines with path and status fields are easier to filter than the access format, not harder. The prompt's only assumption is that the route and status are in the logs.
Which database does the test run on?
SQLite by default, as the first matrix entry of test-backend.yml does, which needs no server. When the alert came from a Postgres deployment the agent also starts Postgres and reruns the file with DB_ENGINE=postgres and --extra pgsql, and the pull request says so.
Why does it install with UV_FROZEN?
Because [tool.uv] in pyproject.toml sets exclude-newer to five days, so any uv command that resolves would fail on pins inside that window. The workflow sets UV_FROZEN=1 for the same reason, and the agent installs exactly the lockfile.
Does it run the frontend or end-to-end tests?
No. The routes here are backend code, and the prompt asks for the backend workflow's steps. If the alert turns out to be a frontend request shape, the agent says so in the pull request and leaves the Nuxt side to a separate run.