{"id":"f9148680642c2705","repo":"koajs/koa","slug":"non-error-thrown-j","errorCode":null,"errorMessage":"non-error thrown: %j","messagePattern":"non-error thrown: (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"critical","filePath":"lib/application.js","lineNumber":245,"sourceCode":"    context.state = {}\n    return context\n  }\n\n  /**\n   * Default error handler.\n   *\n   * @param {Error} err\n   * @api private\n   */\n\n  onerror (err) {\n    // When dealing with cross-globals a normal `instanceof` check doesn't work properly.\n    // See https://github.com/koajs/koa/issues/1466\n    // We can probably remove it once jest fixes https://github.com/facebook/jest/issues/2549.\n    const isNativeError =\n      Object.prototype.toString.call(err) === '[object Error]' ||\n      err instanceof Error\n    if (!isNativeError) { throw new TypeError(util.format('non-error thrown: %j', err)) }\n\n    if (err.status === 404 || err.expose) return\n    if (this.silent) return\n\n    const msg = err.stack || err.toString()\n    console.error(`\\n${msg.replace(/^/gm, '  ')}\\n`)\n  }\n\n  /**\n   * Help TS users comply to CommonJS, ESM, bundler mismatch.\n   * @see https://github.com/koajs/koa/issues/1513\n   */\n\n  static get default () {\n    return Application\n  }\n}\n","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/koajs/koa/blob/52d5e8ff5ac79f2479463b53df2999900ae95115/lib/application.js#L227-L263","documentation":"Thrown by Koa's default application error handler (app.onerror / ctx.onerror) when something was thrown that is not a native Error instance. Koa treats every thrown value as a potential error to surface, but the default handler is only safe to inspect Error objects (reading .status, .expose, .stack); a non-error value (string, number, plain object, or a Promise-rejection with a non-Error reason) is re-thrown as a TypeError so the bug is not silently swallowed. The cross-global check (Object.prototype.toString.call(err) === '[object Error]') exists because instanceof Error fails across vm/jest globals (issue #1466). Hitting this means somewhere in your middleware chain (or a dependency) code does throw 'foo' or throw {} instead of throw new Error('foo').","triggerScenarios":"A middleware or downstream function executes `throw 'not found'`, `throw 404`, `throw { status: 404, message: 'x' }`, or rejects a Promise with a non-Error reason (Promise.reject('boom')). A third-party dependency (e.g. an older ORM or driver) throws a plain object/string. ctx.throw() / ctx.assert() are NOT causes — those construct HttpError instances. The trigger is raw throw of a primitive/object inside a request, surfaced through fnMiddleware(ctx).then(...).catch(onerror) -> ctx.onerror -> app.onerror.","commonSituations":"Legacy JS code ported to Koa that throws strings ('throw \"Unauthorized\"'); numeric HTTP-status throws (`throw 401`) written by devs confusing ctx.throw(401); libraries that reject with strings/objects rather than Error (some redis/db drivers, older validators); code that wraps errors incorrectly (`catch(e){ throw e.message }`); Jest/VM environments where a thrown Error fails the cross-global instanceof check is NOT the cause here because the toString fallback covers it — the cause is genuinely a non-Error thrown value.","solutions":["Search the codebase and dependencies for non-Error throws: patterns `throw '`, `throw \"`, `throw {`, `throw 40`, `throw 50`, and `Promise.reject(` with a non-Error argument; replace each with `throw new Error(...)` (or an http-errors code via ctx.throw).","In Koa code, never throw raw primitives — use ctx.throw(status, msg) or `throw createError(404, 'not found')` so a real HttpError is produced.","If a third-party dependency throws non-Errors, wrap its calls: try { await lib.x() } catch (e) { throw e instanceof Error ? e : new Error(String(e)) }.","Add an app-level safeguard that normalizes before reaching onerror: process.on('unhandledRejection') and a custom app.on('error') that coerces — but the real fix is at the throw site.","Run with --trace-uncaught / attach a debugger and break on the 'non-error thrown' TypeError to capture the stack back to the original throw."],"exampleFix":"// before\nasync function handler (ctx) {\n  if (!ctx.user) throw 'Unauthorized'        // string thrown -> non-error thrown\n  if (!ctx.user.admin) throw 403              // number thrown -> non-error thrown\n}\n\n// after\nconst createError = require('http-errors')\nasync function handler (ctx) {\n  if (!ctx.user) ctx.throw(401, 'Unauthorized')\n  if (!ctx.user.admin) ctx.throw(403)\n}","handlingStrategy":"type-guard","validationCode":"// Normalize any thrown value into a real Error before Koa's onerror ever sees it.\n// Install as the FIRST middleware so downstream throws pass through it.\napp.use(async (ctx, next) => {\n  try {\n    await next()\n  } catch (err) {\n    if (err instanceof Error) throw err\n    // Coerce primitives/objects into a real Error so app.onerror never hits 'non-error thrown'\n    const e = new Error(typeof err === 'string' ? err : JSON.stringify(err))\n    e.exposed = err && typeof err === 'object' && 'status' in err ? err.status : 500\n    throw e\n  }\n})","typeGuard":"// True cross-global Error guard (mirrors Koa's own check at lib/application.js:242-244)\nfunction isNativeError (e) {\n  return e instanceof Error || Object.prototype.toString.call(e) === '[object Error]'\n}\n\n// Use before re-throwing inside middleware:\n// catch (e) { throw isNativeError(e) ? e : new Error(String(e)) }\n\n// TypeScript narrowing:\n// const isErr = (x: unknown): x is Error =>\n//   x instanceof Error || Object.prototype.toString.call(x) === '[object Error]'","tryCatchPattern":"// App-level safety net: wrap the composed chain so non-Error throws never reach app.onerror\nconst fn = app.compose(app.middleware)\nconst safe = async (ctx, next) => {\n  try { await fn(ctx, next) }\n  catch (err) {\n    throw err instanceof Error ? err : new Error(util.format('non-error thrown: %j', err))\n  }\n}\n\n// Custom error listener that filters by status/expose without ever re-throwing:\napp.on('error', (err, ctx) => {\n  if (!ctx) return\n  if (err.status === 404 || err.expose) return\n  logger.error({ err, reqId: ctx.state.reqId }, 'request error')\n})","preventionTips":["Never throw primitives or plain objects — adopt a project rule: throws are always `new Error()`/`new HttpError()`/`ctx.throw()`.","CI grep for anti-patterns: `throw ['\\\"{0-9]` and `Promise.reject('` to catch non-Error throws in your own code.","Audit third-party drivers/ORMs in use; wrap their calls in a normalizing try/catch (above) so a rogue `throw 'ECONNRESET'` becomes a real Error.","Write a test that forces a non-Error throw inside a dummy middleware and asserts your normalizing middleware converts it — regression-guards the contract.","Attach app.on('error', ...) with a structured logger so when a real Error surfaces you also log ctx.method/path/state for triage; keep the listener non-throwing."],"tags":["runtime","error-handling","middleware","uncaught","type-error"],"analyzedSha":"52d5e8ff5ac79f2479463b53df2999900ae95115","analyzedAt":"2026-08-03T17:44:17.351Z","schemaVersion":2}