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 ObjectView on GitHub (pinned to e2740d5a1b)
Solutions
- Make every handler and middleware return a Response (e.g. `return c.json({...})`) on all code paths
- In middleware, ensure you `await next()` when not short-circuiting, and return a Response (e.g. `return c.text('Unauthorized', 401)`) when you do
- Audit early-return branches (auth, validation, rate limits) to confirm each returns something
- 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
- Always return a Response or c.json/c.text result from handlers on every branch
- Use `await next()` in middleware unless short-circuiting with a returned Response
- Enable TypeScript's noImplicitReturns or add ESLint rules to catch fall-through paths
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
- next() called multiple times
- basic auth middleware requires options for "username and pas
- bearer auth middleware requires options for "token" or "veri
- Unmet condition
- Forbidden
AI-assisted analysis of honojs/hono@e2740d5a1b (2026-08-28).
Data as JSON: /api/errors/924769b52f049f1a.
Report an issue: GitHub.