Claude Code Hono CLAUDE.md AI Coding Cloudflare Workers 2026

Hono Is Now the Default Edge Framework in 2026. Your CLAUDE.md Still Treats It Like Express (2026)

The Prompt Shelf ·

Hono has quietly become the default choice for new edge APIs in 2026 — it runs unmodified on Cloudflare Workers, Deno, Bun, and Node because it only touches Web Standard APIs (Request, Response, fetch), and its release cadence (v4.13.x shipping roughly every one to two weeks, with the new HTTP QUERY method and a Method Not Allowed middleware landing in v4.13.0) shows a project still moving fast, not coasting. The catch: most of that adoption is happening in codebases whose CLAUDE.md was written for Express, or never updated once Hono replaced it.

That matters because Hono’s API looks close enough to Express to fool an agent into pattern-matching the wrong one — and a few of those mismatches don’t warn, they just silently produce the wrong response or the wrong inferred type.

The chaining bug: c.status(201).json(data) doesn’t exist

Express lets you chain res.status(201).json(data) because res.status() returns this. Claude Code carries that habit into Hono code constantly, because it’s the single most common Express idiom in its training data. Hono’s Context.status() does not return the context — it returns void:

// Hono source (context.ts)
status = (status: StatusCode): void => {
  this.#status = status
}
// ❌ Express habit — TypeError: Cannot read properties of undefined (reading 'json')
return c.status(201).json({ id: newId })

// ✅ Hono — status is the second argument to json(), no chaining needed
return c.json({ id: newId }, 201)

c.json(data, status) is the idiomatic form and it’s a single call, not two. If your CLAUDE.md has an Express-derived rule like “always set status before sending the body,” it’s actively pushing Claude Code toward the broken chain instead of the one-liner Hono actually wants.

The middleware onion, and the two silent failure modes

Hono’s middleware model — register middleware1, middleware2, middleware3, and they execute as nested layers around the handler (middleware1 start → middleware2 start → middleware3 start → handler → middleware3 end → middleware2 end → middleware1 end) — looks like Express’s chain, but two behaviors are stricter and neither one throws when Claude Code gets them wrong:

  • next() never throws. Errors that happen downstream are handled by app.onError(), not by a try/catch wrapped around next(). A rule telling Claude Code to wrap every await next() in try/catch produces dead code, not safety.
  • next() doesn’t have a return value to use. Hono’s middleware contract is “call await next() and let it fall through, or return a Response to short-circuit” — never both. The failure mode that actually breaks requests: an early-return guard that exits without returning a Response.
// ❌ Silent 404-less failure — stops the chain but sends nothing
if (!authorized) return

// ✅ Every early exit needs a Response
if (!authorized) return c.text('Unauthorized', 401)

Neither of these throws a build error. The first one wastes a few lines; the second one ships a middleware that hangs the request until something upstream (Workers’ own runtime, or a client timeout) kills it.

RPC mode: the type inference breaks before the request does

Hono’s RPC client (hc) is the feature that makes it more than “Express for the edge” — it shares types between server and client with zero codegen, as long as the server exports its route type:

// server
const route = app.post('/users', zValidator('json', schema), (c) => c.json({ id: 1 }, 201))
export type AppType = typeof route

// client
import { hc } from 'hono/client'
const client = hc<AppType>('https://api.example.com')

Three ways Claude Code breaks this without a syntax error:

  1. Using c.notFound() in a handler the client calls via RPC. The type inference tool can’t see through the built-in helper, so the client infers the response as unknown instead of your actual shape. Use c.json({ error: 'not found' }, 404) in any route the RPC client touches — same wire response, but the type survives.
  2. Passing a relative path to $url(). Hono’s $url() requires an absolute base URL; a relative one throws at call time, not at compile time.
  3. Chaining .then() instead of awaiting the client call. Hono’s RPC types are inferred through the await expression. A .then() chain type-checks as any in enough TypeScript configurations that the whole point of RPC mode — catching a renamed field or changed status code at build time — silently stops working.

There’s a fourth failure that isn’t a code mistake at all: a version mismatch between the hono package used by the server and the one used by the client produces a Type instantiation is excessively deep and possibly infinite error. If Claude Code hits that error and starts adding // @ts-ignore around the RPC call, the actual fix is npm ls hono across both workspaces in the monorepo, not a type suppression.

c.env, not process.env

One more habit that Express-trained code carries over without a compiler complaint: reading Cloudflare bindings (KV namespaces, D1, R2, secrets) through process.env. On Workers, that object doesn’t exist the way it does on Node — bindings arrive through the Hono Context, typed by the Bindings generic:

type Bindings = { DB: D1Database; API_KEY: string }
const app = new Hono<{ Bindings: Bindings }>()

app.get('/users', async (c) => {
  const key = c.env.API_KEY // ✅ from the Context
  // const key = process.env.API_KEY  ❌ undefined on Workers, works locally with Node adapters — the gap only shows up in prod
})

This one is worse than the others because it can pass local testing under @hono/node-server and then fail only in the deployed Workers runtime, which is exactly the kind of gap a CLAUDE.md rule should close before it reaches a PR.

The CLAUDE.md block worth adding

## Hono Conventions

- Never chain `c.status(x).json(y)``c.status()` returns void. Use `c.json(y, x)` instead.
- Every middleware early-return must return a `Response` (`c.json()`, `c.text()`, etc.).
  A bare `return` inside middleware hangs the request instead of rejecting it.
- Do not wrap `await next()` in try/catch — next() never throws. Handle errors in `app.onError()`.
- Any route called through the RPC client (`hc<AppType>`) must not use `c.notFound()`
  use `c.json({ error }, 404)` so the client's type inference doesn't collapse to `unknown`.
- Await RPC client calls directly. Do not chain `.then()` — it breaks response type inference.
- Read Cloudflare bindings via `c.env`, never `process.env`. `process.env` is undefined on
  the deployed Workers runtime even if it works during local `node-server` testing.
- Before adding `// @ts-ignore` near an `hc<AppType>()` call, run `npm ls hono` in every
  workspace — a version mismatch between server and client is the usual cause of
  "Type instantiation is excessively deep" errors, not a real type bug.

Where this fits

This is specifically the Express-to-Hono gap — it doesn’t replace a general Cloudflare Workers setup. If you’re also deploying to Workers directly, our Cloudflare Workers CLAUDE.md guide covers bindings, wrangler.toml, and the Workers runtime constraints Hono itself doesn’t own. For Durable Objects-backed agents on the same platform, see our Cloudflare Agents SDK guide. For more frameworks and runtimes with CLAUDE.md and AGENTS.md examples, our gallery has the full set.

Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules