koajs/koa · error · TypeError
non-error thrown: %j
Error message
non-error thrown: %j
What it means
Re-thrown by app.onerror() when middleware, a promise rejection, or a stream error delivers a value that is not a native Error. Koa's error contract requires real Error instances; the handler does a cross-global-safe check (Object.prototype.toString === '[object Error]' || instanceof Error) to cover jest/vm realms, and when that fails it formats the offending value via util.format('%j') and throws a TypeError so the bad value is never silently swallowed. It surfaces from handleRequest's .catch(onerror) chain, meaning anything a middleware throws or rejects with is funneled here.
Solutions
- Find the throw/reject site: search the codebase for throw ' (throw of string literals) and reject( that is not passed a new Error().
- Replace string/number/object throws with Error instances: throw new Error('not found') or better throw Object.assign(new Error('not found'), { status: 404 }).
- Use Koa's built-in ctx.throw(status, msg) which constructs a proper HttpError with status and expose flags.
- If a dependency rejects with non-Errors, wrap the call: try { await lib(x) } catch (e) { throw e instanceof Error ? e : new Error(String(e)) }.
- Add a global normalizing middleware last in the stack that catches ctx errors and re-throws as Error so the app.onerror guard never trips.
Example fix
// before
async function auth(ctx, next) {
if (!ctx.headers.authorization) throw 'unauthorized' // string, not an Error
await next()
}
// after
async function auth(ctx, next) {
if (!ctx.headers.authorization) ctx.throw(401, 'unauthorized') // builds a native HttpError
await next()
} Defensive patterns
Strategy: try-catch
Validate before calling
// normalize anything you are about to throw/reject so app.onerror never sees a non-Error
function toError(value) {
if (value instanceof Error) return value
if (typeof value === 'object' && value !== null && typeof value.message === 'string') {
return Object.assign(new Error(value.message), value)
}
return new Error(String(value))
}
// before rejecting
if (!ok) throw toError(reason) Type guard
// cross-global-safe native Error check matching Koa's own guard
function isNativeError(err) {
return (
Object.prototype.toString.call(err) === '[object Error]' ||
err instanceof Error
)
}
if (!isNativeError(thrownValue)) {
// re-wrap before it reaches app.onerror
throw Object.assign(new Error(String(thrownValue)), { original: thrownValue })
} Try / catch
// terminal normalizing middleware: register LAST so app.onerror only ever sees native Errors
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
if (Object.prototype.toString.call(err) === '[object Error]' || err instanceof Error) {
throw err // already compliant
}
// wrap strings, numbers, plain objects
const wrapped = new Error(typeof err === 'string' ? err : JSON.stringify(err))
wrapped.status = (err && err.status) || 500
wrapped.expose = false
throw wrapped
}
}) Prevention
- Never throw primitives: ban throw '<string>' and throw <number> via lint rules (e.g. eslint no-throw-literal with throwAny: false where supported).
- Use ctx.throw(status, message) for HTTP errors so Koa always constructs a native HttpError for you.
- Reject promises only with new Error(...) or a subclass; reject('msg') will reach onerror as a non-Error.
- When calling third-party libraries that may reject with non-Errors, wrap them in try/catch and re-throw via toError().
- Register a terminal normalizing catch middleware so a stray non-Error from any dependency never crashes app.onerror.
When it happens
Trigger: Any middleware doing throw 'not found', throw 404, throw { status: 400, message: 'bad' }, or return Promise.reject('nope'). A third-party dependency that rejects with a string or plain object. Code that throws the value of an env var or a parsed JSON number. ctx.throw() itself is safe, but a custom helper that does throw res.statusCode (a number) triggers it. A stream piped to the response emitting a non-Error 'error' event.
Common situations: Porting Express-style throw 'bad request' patterns to Koa. Rejecting promises with status codes (reject(401)) in auth helpers. Older Node patterns of throw 'message'. Libraries upgraded to reject with custom error-like objects that fail the cross-global check under jest/jsdom. Assertion libraries that throw non-Error values in certain modes.
Related errors
AI-assisted analysis of koajs/koa@571938d1b4 (2026-08-04).
Data as JSON: /api/errors/f9148680642c2705.
Report an issue: GitHub.
Appendix: source
Thrown at lib/application.js:260
context.state = {}
return context
}
/**
* Default error handler.
*
* @param {Error} err
* @api private
*/
onerror (err) {
// When dealing with cross-globals a normal `instanceof` check doesn't work properly.
// See https://github.com/koajs/koa/issues/1466
// We can probably remove it once jest fixes https://github.com/facebook/jest/issues/2549.
const isNativeError =
Object.prototype.toString.call(err) === '[object Error]' ||
err instanceof Error
if (!isNativeError) { throw new TypeError(util.format('non-error thrown: %j', err)) }
if (err.status === 404 || err.expose) return
if (this.silent) return
const msg = err.stack || err.toString()
console.error(`\n${msg.replace(/^/gm, ' ')}\n`)
}
/**
* Help TS users comply to CommonJS, ESM, bundler mismatch.
* @see https://github.com/koajs/koa/issues/1513
*/
static get default () {
return Application
}
}
View on GitHub (pinned to 571938d1b4)