All jobs

Add JWT Auth to a Hono API from a Linear Issue

Yes. An agent picks up the Linear issue, protects your Hono API's write routes with Hono's jwt middleware, adds tests and opens a pull request.

Run this on RushIn a Project that has your repository.

What the agent does

The agent starts from the issue and the repository, cloned into its own sandbox, and reads how requests reach the routes before it adds middleware, because in Hono the order of app.route and use calls decides what a middleware covers. In the blog example of honojs/examples (a Cloudflare Worker on hono ^4.2.4 with a KV namespace bound as BLOG_EXAMPLE), src/api.ts defines GET /posts, POST /posts, GET, PUT and DELETE /posts/:id on its own Hono instance, and src/index.ts mounts it with app.route('/api', api). The same file already guards those writes: a middleware app runs basicAuth({ username: c.env.USERNAME, password: c.env.PASSWORD }) on /posts/* for any method other than GET, and is mounted on /api before api. So the issue is really "replace basic auth with JWT", and the agent says that in its plan.

It also finds why nobody noticed the basic auth: the tests never see it. src/api.test.ts imports worker from '../src/api', the inner app, not src/index.ts, and calls worker.request('/posts', ...) with env from cloudflare:test, so all ten tests run with no auth in front of them and POST /posts returns 201 without credentials. A JWT check added in index.ts the same way would be just as untested. The agent therefore writes the new tests against the app as deployed, src/index.ts with its /api prefix, or moves the guard into api.ts beside the routes it protects, and states which in the pull request.

The change uses Hono's built-in jwt middleware from hono/jwt. Because the secret is a Worker binding and only exists per request, the middleware is created inside a handler, (c, next) => jwt({ secret: c.env.JWT_SECRET, alg: 'HS256' })(c, next), on POST /posts and on PUT and DELETE /posts/:id, leaving the two reads public as the issue asks. JWT_SECRET replaces USERNAME and PASSWORD in the Bindings type in src/bindings.ts, a .dev.vars entry covers wrangler dev, and the README says to set it with wrangler secret put JWT_SECRET. The agent checks the body of the 401 the middleware returns, and if it is not the { error, ok: false } JSON every other error in this API returns, adds an onError handler so clients get one shape.

Then it runs the tests the way the example is set up. vitest.config.ts uses @cloudflare/vitest-pool-workers with wrangler: { configPath: './wrangler.toml' }, and only wrangler.example.toml is committed, so the agent copies it as the README says and gives the test pool a JWT_SECRET binding. Tests sign tokens with sign from hono/jwt and check: a write with no token answers 401, a write with a token signed by another secret answers 401, an expired token answers 401, a valid token creates, updates and deletes a post, and GET /posts still needs nothing. It runs npm test, which is vitest --run, against the Workers runtime locally, with no Cloudflare account. The blog package has no build script, so it runs tsc --noEmit and wrangler deploy --dry-run to show the Worker compiles and bundles, and says so instead of claiming a build ran.

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: \"Add JWT Auth to a Hono API. In the blog example, protect the create, update and delete routes with Hono's jwt middleware and return 401 without a valid token.\" Before changing anything, trace how requests reach those routes: every app.route and use call, any auth already in front of them, and whether the existing tests exercise the app as deployed or an inner router. If another auth already guards the routes, replace it and say so. Use the jwt middleware from hono/jwt with the secret read from a Worker binding at request time and an explicit alg, keep the read routes public, update the Bindings type, .dev.vars and README, and make the 401 body match the API's other errors. Write tests through the deployed entry point that sign tokens with hono/jwt: no token, wrong secret, expired token and valid token for each protected route, and a public read. Run the example's test script and, since it may have no build script, tsc --noEmit and wrangler deploy --dry-run, until they pass. Open a pull request that links back to the issue and lists what was already there, what changed and why.",
  "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 that links the Linear issue. For the blog example it changes src/index.ts (the basicAuth guard replaced by jwt, or moved into src/api.ts), src/bindings.ts (JWT_SECRET in place of USERNAME and PASSWORD), vitest.config.ts for the test binding, the README's setup steps, and the tests. The description says plainly that the write routes were already protected by basic auth, that the existing tests never went through it, and how the new ones do.

The proof is the test output in the session's transcript, which keeps every command and its result: npm install, each npm test run against the Workers runtime, tsc --noEmit and wrangler deploy --dry-run, including any that failed before a fix. If your repository runs checks on pull requests, they run on this one too.

Before merging, a reviewer checks that each protected route answers 401 without a token in the tests, not only in the description; that the secret is read from a binding and never committed (.dev.vars belongs in .gitignore); that alg is pinned so a token signed with another algorithm is refused; and that whatever issues tokens to clients today is in place before basic auth goes away.

Run it on every Linear issue labelled agent

In the console, an automation on the Project with a Linear trigger on linear.issue.labeled for the label agent starts a run for each issue that gets the label, in the Project's repository, with the issue as context. Auth changes like this one are worth a label because the issue says exactly which routes, and the pull request shows exactly which tests prove it.

Outside the console, your server sends one POST /o/{org}/sessions per issue, with the repository and the https://linear.app/<workspace>/issue/<ID> URL both in resources; Rush reads the issue through your Linear connection. When the turn finishes, a webhook on the API key posts run.completed or run.failed with the session_url.

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

Why run it on Rush?

Testing auth on a Worker means running it in the Workers runtime with its bindings, a KV namespace and a secret, and signing real tokens against it. @cloudflare/vitest-pool-workers does that locally, and in a Rush sandbox the agent installs it, copies the wrangler config, sets a test secret and runs the suite on its own machine, with no Cloudflare account and nothing deployed.

The value of the result is what it shows a reviewer: not only that JWT checks were added, but that the old guard was there, that the old tests skipped it, and that the new tests go through the same entry point production does. All of that is in the transcript with the commands and their output, next to a pull request linked from the issue.

Frequently asked questions

Where does the JWT secret come from?

From a Worker binding, JWT_SECRET, read from c.env on each request. Locally it is set in .dev.vars for wrangler dev and as a test binding in the Vitest pool; in production you set it with wrangler secret put JWT_SECRET. The secret is never written into the code or wrangler.toml.

Will it also issue tokens?

Not unless the issue asks. This issue is about checking tokens, so the pull request protects the routes and the tests sign their own tokens with hono/jwt. If your clients need a login route that returns a token, that is a separate issue with its own design choices.

Why did the existing tests pass without credentials?

In the blog example they import src/api.ts, the inner router, while the auth middleware is mounted in src/index.ts. Requests in those tests never pass through it. The agent's tests go through the deployed entry point so a missing guard fails a test.

Does it work for Hono on Bun or Node, not only Workers?

Yes. The jwt middleware is the same; what changes is where the secret comes from (an environment variable instead of a binding) and how the tests run (bun test or Vitest without the Workers pool). The agent reads the runtime from the repository and follows it.