What the agent does
The agent reads the issue and the workspace it names. In a repository shaped like honojs/examples, the root package.json declares npm workspaces with npm@10.0.0 as the package manager and no lockfile, and the README runs each example with npm -w <workspace> run dev. The basic workspace is a Worker: package.json depends on hono with a caret on 4.2.4, with wrangler, vitest and @cloudflare/workers-types for development and vitest --run as the test script; wrangler.toml names it hono-example-basic with a 2024-04-01 compatibility date; and vitest.config.ts turns on globals.
src/index.ts is one file. It mounts poweredBy on every route, basicAuth on /auth/* and etag on /etag/*, two custom middlewares that add X-message and X-Response-Time headers, a custom notFound and onError, and then the routes: /, /hello, /entry/:id, a nested book router, /redirect, /fetch-url, /user-agent, /api/posts with prettyJSON on GET and a 201 on POST, and /error. src/index.test.ts already shows the testing pattern: app.request('http://localhost/') and assertions on status and headers, with no server started.
The middleware is written in the repository's own style rather than pulled from a package. src/rate-limit.ts exports rateLimit({ limit, windowMs }), a fixed-window counter in a Map keyed on the CF-Connecting-IP header, which Cloudflare sets on every request, with a fallback key for wrangler dev where it is absent. Each response gets RateLimit-Limit and RateLimit-Remaining headers; a request over the limit gets a 429 with a Retry-After header through c.text, consistent with the c.text answers the file already uses for 404 and 500. In src/index.ts it is mounted with app.use('/api/*', rateLimit({ limit: 60, windowMs: 60_000 })) ahead of the /api/posts routes, so the read-only routes and the authenticated /auth/* routes are not counted.
Tests sit beside the existing one. src/rate-limit.test.ts builds a small app with the middleware, sends limit requests with one CF-Connecting-IP and asserts they pass with a decreasing RateLimit-Remaining, sends one more and asserts 429 with Retry-After, sends a request from a second address and asserts it is unaffected, and uses vi.useFakeTimers to move past the window and assert the counter reset. src/index.test.ts gains a case that GET /api/posts carries the limit headers. The agent runs npm -w basic run test and starts npm -w basic run dev to confirm the headers appear from wrangler dev before it pushes.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "A Linear issue labeled agent says: \"Rate limit the /api routes of the basic Worker: 60 requests per minute per client IP, with RateLimit-Limit and RateLimit-Remaining headers on every response and a 429 with Retry-After over the limit. Leave the other routes alone.\" Work in the basic workspace. Read the root package.json, basic/package.json, basic/wrangler.toml, basic/vitest.config.ts, basic/src/index.ts and basic/src/index.test.ts first. Write the middleware in basic/src/rate-limit.ts keyed on CF-Connecting-IP with a fallback for local dev, mount it on /api/* only, add Vitest tests that cover the limit, the headers, a second client and the window reset with fake timers, run npm -w basic run test, confirm the headers with wrangler dev, and open a pull request that links the issue.",
"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, scoped to the basic workspace. The diff adds basic/src/rate-limit.ts and basic/src/rate-limit.test.ts, mounts the middleware in basic/src/index.ts above the /api/posts routes, and adds one assertion to basic/src/index.test.ts. No dependency changes, because the middleware uses only Hono's MiddlewareHandler type and a Map. The description quotes the issue, names the limit and window, shows the headers from one wrangler dev request, and states plainly that the counter is per isolate.
The proof is in the session. The trajectory at GET /o/{org}/sessions/{id}/messages keeps the npm -w basic run test output with each test case named, and the wrangler dev request that shows RateLimit-Limit, RateLimit-Remaining and, after the limit, 429 with Retry-After. This repository has no CI workflow for the examples, so the trajectory is the test record a reviewer reads.
Before merging, a reviewer checks that the middleware is mounted on /api/* and nowhere else, that the key falls back sensibly when CF-Connecting-IP is missing so local development is not locked out, and that the window reset test uses fake timers rather than sleeping. Nothing reaches main until the pull request merges, and nothing is deployed, since npm run deploy is never run.
Run it on every Linear issue labelled agent
Sign in to Linear once with POST /me/connections {"type": "app", "name": "linear"} and finish the authorize URL in the console. Then add an automation to the Project with this prompt and a linear trigger on linear.issue.labeled, naming the label in config.labels. The trigger adds the issue after the prompt and fills in no placeholder, so the agent reads the workspace, the limit and the window from the issue. The automation's max_runs_per_hour, between 1 and 30, caps how many labelled issues it takes on in an hour, each in its own sandbox with its own pull request.
From your own server instead, a handler that receives Linear's webhook for the label sends POST /o/{org}/sessions with the repository and https://linear.app/<workspace>/issue/ENG-42 in resources, which puts the issue text in the prompt through your Linear connection, and a request_id derived from the issue id. Give that key a webhook_url and Rush POSTs run.completed or run.failed when the turn ends, signed per Standard Webhooks, with data.session_url to read for the pull request link.
The use case this job follows, with its automation and trigger: Turn a Linear label into a PR.
Why run it on Rush?
A rate limiter is a small amount of code with a precise contract, and the contract is only known to hold when a test sends the sixty-first request and reads the headers. On a laptop that test is the thing that gets skipped because the change looks obvious. On Rush the agent writes it in its own sandbox, runs vitest against app.request with no server and wrangler dev with one, and pushes only when both agree.
The pull request is also the record. The diff is small enough to read in one sitting, the test names say what was checked, and the trajectory keeps the test output and the live request. Several labelled issues become several sandboxes and several pull requests, each with its own evidence, instead of a queue on one machine.
Frequently asked questions
Why not use hono-rate-limiter?
It would work: hono-rate-limiter 0.5 asks for Hono 4.10.8 or newer, and with no lockfile a fresh install of the caret range resolves to the current 4.x line. The issue asks for a fixed window and three headers, which is a few dozen lines with no store to configure, so the agent writes it in the repository's style and keeps the dependency list as it is. Say so in the issue if you prefer the package, and the agent uses it instead.
Is the limit global across Cloudflare's network?
No. A Map lives in one Worker isolate, so the count is per isolate and resets when the isolate is recycled. For a limit that holds across locations, the repository's durable-objects example shows the pattern to move the counter into a Durable Object, and Cloudflare also offers a rate-limiting binding. The pull request states the per-isolate scope plainly so nobody mistakes it for a global quota.
Which routes are limited?
Only /api/*, which is where /api/posts lives. The middleware is mounted with app.use('/api/*', ...) before those routes, so /, /hello, /book, /auth/* and the rest are unchanged, and src/index.test.ts keeps proving GET / answers with x-powered-by: Hono and no limit headers.
How does it identify a client?
By the CF-Connecting-IP request header, which Cloudflare sets at the edge. Under wrangler dev that header is absent, so the middleware falls back to a single key, which the test for the second client exercises by setting the header explicitly. A reverse proxy in front of the Worker would need its own header, and the key function is one line to change.