← Home ← Codex ← DEBT ← Engine
Browse by Category
+ added · updated 7d
← Back to glossary

Error.cause — Error Chaining

JavaScript ES2022 Beginner
debt(d7/e1/b3/t5)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The detection_hints.tools list is empty. The common mistakes — forgetting to pass { cause: e }, setting cause to a string, or logging only err.message — are not caught by syntax checks or default linters. A developer must manually review re-throw sites or notice missing context in production logs. No standard static analysis rule flags missing cause in catch blocks, so this lives firmly in code-review territory.

e1 Effort Remediation debt — work required to fix once spotted

Closest to 'one-line patch or single-call swap' (e1). The quick_fix is explicit: adding '{ cause: e }' to an existing throw statement is a single-argument addition per re-throw site. Each fix is one line; while multiple sites may exist, each individual correction is trivial.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3). The applies_to scope is web and cli, which is broad, but the concept is opt-in per catch block rather than a load-bearing architectural choice. It imposes a discipline tax — developers must remember the pattern at every re-throw site — but it doesn't shape the system's architecture or slow down unrelated work streams. The logging/serialisation concerns (toJSON, serialize-error) add a small persistent cost in error-handling infrastructure.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception field calls out that Error.cause is NOT automatically displayed in console.error() across all environments — browsers may silently omit it. Combined with the common mistake of forgetting { cause: e } entirely (silently losing stack context) and the silent no-op on Node.js < 16.9, these are documented gotchas that developers discover the hard way but are not catastrophically counter-intuitive to the concept's name.

About DEBT scoring →

Also Known As

Error.cause error chaining JS error wrapping JavaScript

TL;DR

ES2022 added a cause option to the Error constructor — 'new Error('message', { cause: originalError })' — enabling proper error chaining where a high-level error wraps its underlying cause, preserving the full error context across abstraction layers.

Explanation

Before error cause, rethrowing with context required storing the original error separately or appending its message to the new one, losing the original stack trace. Error cause standardises this: any Error constructor accepts a second options object with a cause property. The cause can be any value — another Error, a string, an object. It is accessible as err.cause. Error.cause enables proper error chains: a network error causes a fetch error causes an authentication error. Logging and monitoring tools (Sentry, Datadog) surface the full chain. Node.js 16.9+ and all modern browsers support it.

Watch Out

Error.cause is not enumerable by default, so JSON.stringify(error) will not include it — you must manually serialize err.cause or use a custom replacer function, which trips up developers expecting the cause to appear in error logs automatically.

Common Misconception

Error.cause is automatically displayed in console.error(). Console output varies by environment — Node.js prints the cause chain; browsers may not. Always explicitly log or report err.cause in error handlers.

Why It Matters

Without error cause, catching a low-level error and throwing a higher-level one loses context. 'Failed to load user profile' tells you what failed but not why — was it a network timeout? A 401? A JSON parse error? Error.cause lets you carry the original error through abstraction layers so your error handler sees the full picture.

Common Mistakes

  • Not passing { cause: e } when rethrowing — the most common mistake; catching and throwing a new Error without cause loses the original stack entirely.
  • Setting cause to a string instead of an Error — err.cause = 'some string' loses the stack trace; always use the original Error object as cause.
  • Not checking for cause support in old Node.js — Error.cause requires Node.js 16.9+; on older versions the option is silently ignored.
  • Logging only err.message in error handlers — always log err.cause as well, or use a logging library that serialises the full cause chain.
  • Losing the cause through JSON.stringify — cause is not serialised by default; implement toJSON() on your Error subclass or use a library like serialize-error.

Avoid When

  • When the original error is already logged or will never be inspected — wrapping adds noise without benefit.
  • When chaining deeply (more than 3–4 levels) — the chain becomes hard to traverse and obscures the root cause.
  • When the cause is user-facing or sensitive data that should not leak into error logs or monitoring systems.
  • When building a cause from a non-Error value (string, number) without validation — the cause becomes ambiguous and hard to inspect downstream.

When To Use

  • Wrap low-level errors (network, database, file system) in domain-specific errors without losing the original stack trace and debugging context.
  • Build error chains across abstraction layers (API client wraps HTTP errors, service layer wraps client errors, controller wraps service errors) so monitoring tools and logs show the full causal chain.
  • Distinguish between error types at different levels (distinguish 'DatabaseConnectionError caused by timeout' from 'DatabaseConnectionError caused by authentication failure') for targeted error handling and recovery logic.
  • Preserve original Error objects in async/await code where errors pass through multiple try-catch blocks, ensuring the root cause remains inspectable for production debugging and incident analysis.

Code Examples

💡 Note
Bad code loses the original error details by rethrowing with only a new message; good code preserves the full error chain using the cause option, making debugging across layers straightforward.
✗ Vulnerable
// ❌ Losing the original error — only the new message survives
async function loadUser(id) {
    try {
        const resp = await fetch(`/api/users/${id}`);
        return await resp.json();
    } catch (e) {
        // Original error (e.g. NetworkError, SyntaxError) is thrown away
        throw new Error(`Failed to load user ${id}`);
    }
}

// ❌ Appending message — ugly, loses stack trace
} catch (e) {
    throw new Error(`Failed to load user ${id}: ${e.message}`);
}
✓ Fixed
// ✅ Error.cause — full chain preserved
async function loadUser(id) {
    try {
        const resp = await fetch(`/api/users/${id}`);
        if (!resp.ok) {
            throw new Error(`HTTP ${resp.status}`, { cause: resp });
        }
        return await resp.json();
    } catch (e) {
        throw new Error(`Failed to load user ${id}`, { cause: e });
    }
}

// Error handler sees the full chain
try {
    await loadUser(42);
} catch (e) {
    console.error(e.message);       // 'Failed to load user 42'
    console.error(e.cause.message); // 'HTTP 404'
    // Sentry / Datadog get the full chain automatically
}

// Traversing the cause chain
function getRootCause(err) {
    return err.cause ? getRootCause(err.cause) : err;
}

Added 23 Mar 2026
Edited 18 Jul 2026
Views 119
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings W 0 pings T 0 pings F 0 pings S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 1 ping F 0 pings S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 1 ping S 1 ping M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T
No pings yet today
No pings yesterday
Amazonbot 15 SEMrush 9 Google 7 Ahrefs 7 Bing 7 Perplexity 3 Scrapy 3 Applebot 2 ChatGPT 1 Majestic 1 Meta AI 1 PetalBot 1 Twitter/X 1 Brave Search 1
crawler 57 crawler_json 2
🧱 FUNDAMENTALS — new to this? Start with the ground floor.
JavaScript javascript The programming language of the browser — it reads and modifies the page (the DOM), reacts to user events, and fetches data without reloading.

JavaScript is the only language browsers execute, so every interactive behaviour on the web goes through it. Its two defining traits — single-threaded event loop and loose typing (== coercion) — explain the majority of both its bugs and its design patterns.

💡 Default to const, use === always, and reach for let only when a value genuinely reassigns.

Ask Codex about JavaScript →
DEV INTEL Tools & Severity
⚙ Fix effort: Low
⚡ Quick Fix
When catching and re-throwing, always pass the caught error as cause: 'throw new AppError('Operation failed', { cause: e })' — this one habit preserves all error context.
📦 Applies To
javascript ES2022 web cli
🔗 Prerequisites
⚠ Related Problems


✓ schema.org compliant