honojs/hono · error · Error

Context is not finalized. Did you forget to return a Respons

Error message

Context is not finalized. Did you forget to return a Response object or `await next()`?

What it means

This error is thrown by Hono's dispatcher when, after all matching middleware and handlers run, the Context has not been finalized — meaning no Response object was ever set. It almost always means a middleware or handler completed without returning a Response and without awaiting next(), so the composed chain ended with an unresolved context.

Source

Thrown at src/hono-base.ts:457

      }

      return res instanceof Promise
        ? res
            .then(
              (resolved: Response | undefined) =>
                resolved || (c.finalized ? c.res : this.#notFoundHandler(c))
            )
            .catch((err: Error) => this.#handleError(err, c))
        : (res ?? this.#notFoundHandler(c))
    }

    const composed = compose(matchResult[0], this.errorHandler, this.#notFoundHandler)

    return (async () => {
      try {
        const context = await composed(c)
        if (!context.finalized) {
          throw new Error(
            'Context is not finalized. Did you forget to return a Response object or `await next()`?'
          )
        }

        return context.res
      } catch (err) {
        return this.#handleError(err, c)
      }
    })()
  }

  /**
   * `.fetch()` will be entry point of your app.
   *
   * @see {@link https://hono.dev/docs/api/hono#fetch}
   *
   * @param {Request} request - request Object of request
   * @param {Env} env - env Object

View on GitHub (pinned to e2740d5a1b)

Solutions

  1. Make every handler and middleware return a Response (e.g. `return c.json({...})`) on all code paths
  2. In middleware, ensure you `await next()` when not short-circuiting, and return a Response (e.g. `return c.text('Unauthorized', 401)`) when you do
  3. Audit early-return branches (auth, validation, rate limits) to confirm each returns something
  4. If you wrapped the app in custom compose/error logic, verify the errorHandler returns a Response

Example fix

// before
app.use('/admin/*', async (c, next) => {
  if (!c.req.header('Authorization')) return // falls through, nothing set
  await next()
})
// after
app.use('/admin/*', async (c, next) => {
  if (!c.req.header('Authorization')) {
    return c.text('Unauthorized', 401)
  }
  await next()
})
Defensive patterns

Strategy: validation

Validate before calling

// Ensure every guard branch returns
app.use('/api/*', async (c, next) => {
  const auth = c.req.header('Authorization')
  if (!auth) return c.text('Unauthorized', 401)
  await next()
})

Type guard

null

Try / catch

try {
  const res = await app.request(req)
} catch (e) {
  if (e instanceof Error && /not finalized/.test(e.message)) {
    // a middleware/handler fell through; add missing return/await next()
  }
}

Prevention

When it happens

Trigger: A handler or middleware that returns undefined (no return statement), a middleware that forgets `await next()`, an early-return guard path (e.g. auth check) that returns nothing, or throwing/rejecting inside a custom middleware that is swallowed so no response is produced.

Common situations: Adding an authentication/validation middleware that returns early on failure without returning a Response; refactoring a handler and dropping the return; using a middleware that calls next() without await (fire-and-forget); conditional branches where one path returns c.json() and another falls through.

Related errors


AI-assisted analysis of honojs/hono@e2740d5a1b (2026-08-28). Data as JSON: /api/errors/924769b52f049f1a. Report an issue: GitHub.