What the agent does
The agent clones the repository into its own cloud sandbox and reads how the API is put together before adding a route. In an Express API like gothinkster/node-express-realworld-example-app the project is an Nx 17.2.6 workspace: project.json has build, serve, lint, test and docker-build targets, express is pinned at ~4.18.1, @prisma/client at ^4.16.1 with a postgresql datasource reading DATABASE_URL in src/prisma/schema.prisma, and tests run on Jest 29 with ts-jest. src/main.ts configures cors, body-parser, mounts routes under /api, serves src/assets, answers GET / with { status: 'API is running on /api' }, installs an error handler and listens on PORT or 3000. src/app/routes/routes.ts composes the tag, article, profile and auth controllers. A search for health, ping or readiness finds nothing.
It reads two things that shape the result. src/prisma/prisma-client.ts exports one PrismaClient and caches it on global in development, so a readiness check can import it rather than open a second connection. And src/tests/prisma-mock.ts uses jest-mock-extended to replace that module with mockDeep<PrismaClient>() and reset it before each test, which is how auth.service.test.ts and the other service tests run without a database. The new tests follow that pattern instead of needing Postgres.
The agent adds src/app/routes/health/health.controller.ts with two routes. GET /health/live answers 200 with { status: 'ok' } and touches nothing, so a process that is up but has lost its database still passes liveness and is not restarted into a crash loop. GET /health/ready runs prisma.$queryRaw with SELECT 1 under a short timeout and answers 200 with { status: 'ok', checks: { database: 'up' } }, or 503 with database: 'down' and the error message left out of the body. Both are mounted in src/main.ts before routes, outside the /api prefix and outside express-jwt, so a load balancer needs no token. The existing GET / stays as it is. src/tests/routes/health.controller.test.ts uses prismaMock.$queryRaw.mockResolvedValue for the 200 and mockRejectedValue for the 503, calling the handlers through supertest against a small app that mounts only the health router.
Because the Dockerfile is Nx-generated from node:lts-alpine with CMD ["node", "api"] and no HEALTHCHECK, it adds one that uses BusyBox wget, which the Alpine image has, against /health/live on the PORT the image sets. One thing it finds along the way goes in the pull request rather than the diff: e2e/src/server/server.spec.ts, the Nx scaffold's end-to-end test, expects GET / to return { message: 'Hello API' }, which src/main.ts has never sent. The agent leaves that file alone and notes it. It runs npx nx lint api, npx nx test api and npx nx build api, then opens the pull request.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Add health checks to this Express API. Read src/main.ts, src/app/routes/routes.ts, src/prisma/prisma-client.ts, src/tests/prisma-mock.ts and the Dockerfile first. Add GET /health/live, which answers 200 with no dependencies, and GET /health/ready, which runs SELECT 1 through the existing Prisma client with a timeout and answers 200 or 503 with a checks object; mount both in src/main.ts before the /api routes and outside the JWT middleware, and keep the existing GET /. Write Jest tests that use the existing prisma-mock pattern for both outcomes. Add a HEALTHCHECK to the Dockerfile using wget against /health/live. Change nothing under e2e/, but report anything there that disagrees with the app. Run npx nx lint api, npx nx test api and npx nx build api, and open a pull request.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- 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 on its own branch that adds src/app/routes/health/health.controller.ts and src/tests/routes/health.controller.test.ts, adds two lines to src/main.ts to mount the router, adds a HEALTHCHECK line to the Dockerfile, and adds supertest to the dev dependencies in package.json and package-lock.json. No existing route, service or Prisma file changes. The description shows the JSON each endpoint returns in both states, quotes the Jest summary from npx nx test api, and has a short note about the stale expectation in e2e/src/server/server.spec.ts.
A reviewer checks that /health/live imports nothing from Prisma, so a database outage cannot fail liveness; that the ready check's timeout is shorter than the probe interval a Kubernetes or compose health check would use; and that the 503 body names the failing check without echoing the database error, which can carry a connection string. The session's transcript keeps the lint, test and build output in order.
If the repository's error handler in src/main.ts would turn a rejected ready check into a 500 before the route answers, the agent handles the rejection inside the route and says so, rather than relying on the handler's shape.
Run it on every new service without a health route
When a new API repository is created, a handler on your server receives GitHub's repository event and sends POST /o/{org}/sessions with this prompt and the repository as the resources URL. A script can do the same across existing services by listing them through GET /o/{org}/github and checking each for a health route. Each run works in its own sandbox and opens its own pull request.
Set a webhook_url on the API key and Rush POSTs run.completed when the turn finishes, with the session_url to read the pull request from.
The use case this job follows, with its automation and trigger: Hand off from your terminal.
Why run it on Rush?
Health endpoints are small and the decisions around them are not: what liveness may depend on, how long readiness may wait, where the route sits relative to auth, what the container image can run as a probe. The agent reads those answers out of the repository, writes the routes and the tests in its own sandbox with the Nx targets the project already has, and your checkout is untouched.
The pull request arrives with the test run, the lint and the build in the transcript, and a note about the stale end-to-end test the agent found but did not change. A reviewer reads a focused diff with evidence attached, and several services can get the same treatment at once.
Frequently asked questions
Why two endpoints instead of one /health?
Because they answer different questions. Liveness asks whether the process should be restarted, and a lost database is not a reason to restart Node; readiness asks whether traffic should be routed here, and a lost database is exactly that. Kubernetes, ECS and most load balancers take the two separately, and a single endpoint forces one answer for both.
Does the ready check open a new database connection?
No. It imports the PrismaClient from src/prisma/prisma-client.ts, the same instance every service uses, and runs SELECT 1 through $queryRaw. In tests that module is already replaced by jest-mock-extended, so the new tests run without Postgres like the rest of the suite.
Are the endpoints behind the JWT middleware?
No. They are mounted in src/main.ts before the /api router, outside the express-jwt middleware the auth controller applies, so a probe sends no token. They expose a status word and a check name, nothing about users or data.
Will the Docker HEALTHCHECK work on the Alpine image?
Yes. The image is node:lts-alpine, which has no curl but ships BusyBox wget, and the instruction uses wget -qO- against /health/live on the port the image sets with ENV PORT=3000. The agent builds the image with npx nx docker-build api and checks docker inspect reports the container healthy before it opens the pull request.