All jobs

Turn Linear Issues into Pull Requests for a FastAPI App

Label the issue and an agent implements it in your FastAPI routes and SQLModel models, adds the Alembic migration and pytest tests, and opens a linked pull request.

Run this on RushIn a Project that has your repository.

What the agent does

The run starts with the issue in the prompt, read through your Linear connection from the https://linear.app/<workspace>/issue/<ID> resource, and the repository cloned beside it. For an issue like "add a soft delete for items with a restore endpoint" in an app shaped like fastapi/full-stack-fastapi-template, the agent reads backend/pyproject.toml (Python 3.14, fastapi[standard], sqlmodel, alembic, psycopg, pydantic-settings, and a dev group with pytest, coverage, mypy in strict mode, ty and ruff), uv.lock, and the scripts the project runs: scripts/prestart.sh (alembic upgrade head then app/initial_data.py), scripts/test.sh (coverage run -m pytest tests/ with FASTAPI_ENV=development, then coverage report) and scripts/lint.sh (mypy app, ty check app, ruff check app, ruff format app --check). .github/workflows/test-backend.yml shows the order CI uses: docker compose up -d --wait db mailpit, prestart, tests, and coverage report --fail-under=90.

It then reads the code the issue touches. app/models.py defines Item with a UUID id, title (1 to 255 characters), description, created_at and an owner_id foreign key with ondelete="CASCADE", plus ItemCreate, ItemUpdate, ItemPublic and ItemsPublic. app/api/routes/items.py lists items for the owner (or every item for a superuser) with count and data, and its read_item, update_item and delete_item share the same two guards: 404 "Item not found" and 403 "Not enough permissions". delete_item calls session.delete and returns Message("Item deleted successfully"). The tests in tests/api/routes/test_items.py use the client, superuser_token_headers, normal_user_token_headers and db fixtures from tests/conftest.py and create_random_item from tests/utils/item.py.

The change follows those shapes. Item and ItemPublic gain deleted_at: datetime | None; alembic revision --autogenerate -m "add deleted_at to item" writes a migration in app/alembic/versions/ next to the five that exist, in the same op.add_column form as fe56fa70289e_add_created_at_to_user_and_item.py. read_items and read_item filter on Item.deleted_at == None, delete_item sets deleted_at to get_datetime_utc() instead of deleting the row, and a new POST /items/{id}/restore clears it with the same 404 and 403 guards, returning ItemPublic. A include_deleted query flag on the list is added only if the issue asks for it; otherwise the description names it as a follow-up.

Tests come with the change: a deleted item disappears from the list and answers 404 on GET, restore brings it back and returns the item, a normal user restoring someone else's item gets 403, and the existing delete test's assertion on the message still holds. The agent runs uv run bash scripts/test.sh, keeps coverage above the 90 percent CI requires, runs scripts/lint.sh so strict mypy and ruff pass, and, because frontend/src/client is generated from the OpenAPI schema by scripts/generate-client.sh, regenerates the client so the new route is typed on the frontend too. The pull request description links the issue and quotes the test and coverage lines.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "claude",
  "input": "Do the work the Linear issue given as a resource describes: add a soft delete for items with a restore endpoint. Read the issue and its comments first. Follow this project's patterns: a nullable deleted_at on the Item model and ItemPublic, an Alembic autogenerated migration in app/alembic/versions, the list and read endpoints hiding deleted items, DELETE setting deleted_at instead of removing the row, and a POST /items/{id}/restore route with the same 404 and 403 guards as the other item routes. Add pytest tests beside tests/api/routes/test_items.py for delete, hidden-after-delete, restore and the permission refusal. Start Postgres, run bash scripts/prestart.sh, bash scripts/test.sh and bash scripts/lint.sh, regenerate the frontend client with scripts/generate-client.sh, and open a pull request whose description links the issue and quotes the coverage line. Do not merge.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    },
    {
      "url": "https://linear.app/your-org/issue/ENG-42"
    }
  ]
}
  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 a new branch with changes to backend/app/models.py, backend/app/api/routes/items.py, one new file under backend/app/alembic/versions/, new tests in backend/tests/api/routes/test_items.py, and the regenerated frontend/src/client with the restore operation and the deleted_at field. No other route, model or setting moves.

The description links the issue, states the rule in one line (deleted items are hidden and restorable by their owner or a superuser), and quotes the pytest summary, the coverage report total and the clean lint.sh run. The trajectory keeps the Alembic output, so a reviewer can see the migration was generated from the model rather than typed by hand.

Before merging, a reviewer checks that the migration's downgrade drops the column, that read_items filters deleted rows in both the superuser and the owner branch and in both the count and the data query, that restore on an item that is not deleted is handled deliberately, and that the frontend client diff contains only the generated changes.

Run it on every Linear issue labeled agent

Create an automation in the Project with this prompt and a linear trigger on linear.issue.labeled, config.labels set to your hand-off label. Labeling starts one run with the issue as its context; a bot or integration that labels issues never fires it. Cap max_runs_per_hour so a triage session that labels twenty issues does not start twenty runs at once.

For tickets that need a design check first, add a linear.issue.state_changed trigger whose config.states maps one state to plan and the next to exec: the first run reads the code and comments its plan on the issue, the second does the work. Give the API key a webhook URL and read run.completed to post each pull request link where the team sees it.

The use case this job follows, with its automation and trigger: Turn a Linear label into a PR.

Why run it on Rush?

A FastAPI service with Alembic, strict mypy and a Postgres test database needs the database up, the migrations applied and the first superuser created before one test runs, and the coverage gate means a change without tests fails CI anyway. On Rush the agent does the setup in its own sandbox the way test-backend.yml does, and the pull request arrives with the test, coverage and lint lines already in it.

Every labeled issue becomes its own run with its own record, so a week of small tickets becomes a week of small pull requests, each traceable from the Linear issue to the migration, the tests and the commands that proved them. Nothing waits for a laptop to be free.

Frequently asked questions

Does it write the Alembic migration by hand?

No. It changes the SQLModel model and runs alembic revision --autogenerate against a running Postgres, then reads the generated file to confirm it adds and drops exactly the one column. Hand-written migrations drift from the model; generated ones do not.

Will it keep the 90 percent coverage gate?

Yes. scripts/test.sh runs under coverage, and CI fails below 90 percent, so the agent adds tests for every new branch, including the 403 refusal on restore. The coverage total is quoted in the description.

Does it touch the React frontend?

Only the generated client. scripts/generate-client.sh exports the OpenAPI schema and runs openapi-ts so frontend/src/client knows the new route. A restore button in the UI is a separate issue unless this one asks for it.

How does it get Postgres and the first superuser?

It starts Postgres in the sandbox, sets POSTGRES_*, FIRST_SUPERUSER and FIRST_SUPERUSER_PASSWORD as app/core/config.py expects, and runs scripts/prestart.sh, which applies migrations and creates the superuser the tests sign in as.

What if the issue is underspecified?

The agent picks the reading that matches the existing routes and says so in the pull request description, with the alternative named. It does not post to the issue itself, since a run cannot yet name a Linear connection.