Skip to content

Hono router mis-dispatches POST to the GET handler (or 404) under perry — build-layout-dependent (method value is correct) #5458

Description

@proggeramlug

Summary

In a compiled hono app, a POST request is dispatched to the GET handler (or 404s) — the router selects the wrong handler by method. c.req.method is correct inside the handler, so it's not method-value loss. It is build-layout-dependent: the same routes route correctly or incorrectly depending on unrelated surrounding code, which makes this another manifestation of the heap/codegen-layout memory-corruption family (cf. the "premature empty-200 / dropped dynamic response" reports). This breaks every POST endpoint — in our CMS it makes POST /admin/login unusable (login can't complete).

Environment

  • perry main 0.5.1195 (f30a2f5c8), macOS arm64, hono ^4.10.0.

Repro A — mis-dispatch (compile & run this exact file)

repro.ts:

import { Hono } from 'hono';
const app = new Hono();
app.post('/ponly', (c) => c.text('PONLY'));
app.get('/both', (c) => c.text('GET-both'));
app.post('/both', (c) => c.text('POST-both'));

async function d(label: string, path: string, withBody: boolean): Promise<void> {
  const opts: RequestInit = withBody
    ? { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: 'a=1' }
    : { method: 'POST' };
  const res = await app.fetch(new Request('http://h' + path, opts));
  const t = await res.text();
  console.log(`${label}: status=${res.status} ${JSON.stringify(t.slice(0, 30))}`);
}
async function main(): Promise<void> {
  await d('POST /ponly', '/ponly', false);
  await d('POST /both ', '/both', false);
}
void main();

package.json: { "type":"module", "dependencies":{"hono":"^4.10.0"}, "perry":{"compilePackages":["hono"]} }

Actual:

POST /ponly: status=404 "404 Not Found"      <-- expected 200 "PONLY"
POST /both : status=200 "GET-both"           <-- expected 200 "POST-both" (hit the GET handler!)

c.req.method echoes correctly (verified with an app.all('*') fallback) — so the method value is fine; the router picks the wrong handler.

Repro B — proof it's layout-dependent (the smoking gun)

The identical routes (/ponly, /both) wrapped in a small helper + with the router imports present route correctly:

import { Hono } from 'hono';
import { RegExpRouter } from 'hono/router/reg-exp-router';
import { TrieRouter } from 'hono/router/trie-router';
async function test(name: string, app: Hono) {
  app.post('/ponly', (c) => c.text('PONLY'));
  app.get('/both', (c) => c.text('GET-both'));
  app.post('/both', (c) => c.text('POST-both'));
  const r2 = await app.fetch(new Request('http://h/both', { method: 'POST' }));
  console.log(name, await r2.text());
}
await test('default', new Hono());
await test('regexp', new Hono({ router: new RegExpRouter() }));
await test('trie', new Hono({ router: new TrieRouter() }));

Actual (all correct): default POST-both | regexp POST-both | trie POST-both.

Same routes, same handlers, same router — only the surrounding code differs — yet Repro A mis-routes and Repro B is correct. So this is not a router-logic bug and not RegExpRouter-specific; it's layout/heap-sensitive corruption. Switching routers is therefore not a reliable workaround.

Real-world reliable trigger

The full Skelpo CMS (Hono SSR, ~/projects/skelpo-cms) reproduces deterministically: compile src/server.ts with perry main, boot, then curl -X POST .../admin/login returns an empty 200 (POST mis-dispatched; the actual handler's redirect/Set-Cookie never runs), while GET /admin/login renders fine. Happy to share the binary/build for a layout-stable trigger.

Impact

All non-GET routing is unreliable under sufficiently large/complex builds — POST/PUT/DELETE may hit a GET handler or 404. Blocks login and every mutating endpoint of a Hono app on perry-native.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions