Skip to content

TrieRouter runs a handler twice when a path segment is literally * #5370

Open

Description

@harshit-d3v

A handler runs twice when the last segment of the request path is literally *.

import { Hono } from 'hono'
import { TrieRouter } from 'hono/router/trie-router'

const app = new Hono({ router: new TrieRouter() })
let calls = 0
app.use('/a/*', async (c, next) => { calls++; await next() })
app.all('*', (c) => c.text('ok'))

await app.request('http://localhost/a/b')   // calls === 1
calls = 0
await app.request('http://localhost/a/*')   // calls === 2

/a/b runs the middleware once. /a/* runs it twice.

This is reachable without choosing TrieRouter explicitly. SmartRouter falls back to it whenever RegExpRouter throws UnsupportedPathError, so this app on the default router hits it too:

const app = new Hono()
app.use('/a/*', async (c, next) => { calls++; await next() })
app.get('/a/b', (c) => c.text('static'))
app.get('/a/:p', (c) => c.text('param'))
// getRouterName(app) === 'SmartRouter + TrieRouter'
// GET /a/*  ->  middleware runs twice

Cause

In insert() a pattern node is stored twice: under its own key in #children, and in #patterns.

const child = (curNode.#children[key] ||= new Node())
if (pattern && !child.#pattern) {
  child.#pattern = pattern
  curNode.#patterns.push(child)
}

In search(), a request part of * finds that node through node.#children[part] and pushes its handlers, then the #patterns loop reaches the same node and pushes them again. Any other part misses the #children lookup and only takes the pattern branch, which is why only a literal * duplicates.

The other three routers return the handler once for this path.

I have a fix that skips the pattern branch when it resolves to the node the #children lookup already handled, limited to the last segment so a wildcard still matches the rest of the path. Full suite passes and I added the case to common.case.test.ts, where all four routers pass it. Happy to open the PR.

Activity

  1. MahathirMohammadShuvo commented on Sep 14, 2026

    @MahathirMohammadShuvo
    Contributor

    This is also fixed by #5318, open since Aug 29. I ran the two tests from #5371 against that branch: both pass on every router, and on current main the TrieRouter case matches twice.

  2. yusukebe commented on Sep 14, 2026

    @yusukebe
    Member

    This issue is already fixed by #5318.

    The PR will be merged in the next major release, v5.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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