Unhandled promise rejections are silent killers. In Node.js prior to v15, they produced no output and leaked memory. In modern runtimes they terminate the process, causing unannounced restarts. In browsers, they leave background operations in an unknown state with no user feedback. CWE-703 (improper check for exceptional conditions) applies directly. Without a global handler, async errors from queued jobs, scheduled tasks, or deferred operations vanish entirely — producing outages with no trace in logs and no signal in error tracking.
High because unhandled rejections terminate Node.js processes without warning or logging, causing unannounced service restarts with zero diagnostic trail.
Register global rejection handlers in your app's entry point, before any async code runs. Both browser and Node.js surfaces need coverage.
// app/layout.tsx or lib/error-handlers.ts
if (typeof window !== 'undefined') {
window.addEventListener('unhandledrejection', (event) => {
// Send to error tracking before preventing default
reportError(event.reason)
event.preventDefault()
})
}
// server entry point
process.on('unhandledRejection', (reason) => {
logger.error({ reason }, 'Unhandled promise rejection')
// Do NOT exit — let the process continue unless reason is catastrophic
})
The handler must call into your error tracking service (Sentry, Datadog, etc.), not just console.error, for the check to pass.
ID: error-resilience.logging-observability.unhandled-promise-rejection
Severity: high
What to look for: Count all async operations (Promise chains, async/await) in the codebase. Enumerate which have .catch() or try/catch vs. which could produce unhandled rejections. Search for window.onunhandledrejection (browser) or process.on('unhandledRejection') (Node.js). This handler should catch promise rejections that are not caught by .catch() or try/catch and log them to an error tracking service.
Pass criteria: An unhandled rejection handler is configured that logs rejections to an error tracking service or structured logging system. At least 1 global unhandled rejection handler must be registered, and at least 90% of async operations should have local error handling.
Fail criteria: No unhandled rejection handler found, or a handler exists but does not log to an error tracking service.
Skip (N/A) when: Never — unhandled rejections should always be handled.
Cross-reference: For window.onerror handling, see window-onerror-handler.
Detail on fail: "No unhandledRejection handler configured. Promise rejections not caught by .catch() will be silently ignored" or "Handler exists but logs to console only, not to error tracking service"
Remediation: Add an unhandled rejection handler in your app entry point:
// app/layout.tsx or lib/error-handlers.ts — global handler
if (typeof window !== 'undefined') {
window.addEventListener('unhandledrejection', (e) => { reportError(e.reason) })
}
// For browser/client-side:
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason)
// Send to error tracking service
Sentry.captureException(event.reason)
event.preventDefault() // Prevent app crash
})
// For Node.js/server-side:
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection at:', promise, 'reason:', reason)
// Send to error tracking service
logger.error({ reason, promise }, 'Unhandled rejection')
})