Champion: Ruben Bridgewater
Author: Ruben Bridgewater ruben@bridgewater.de
Stage: 1
This proposal introduces a new per-error Error options property, limit, that specifies the maximum number of implementation defined stack frames to capture for that specific error instance.
limitis a numeric option interpreted using ToIntegerOrInfinity.- If provided, the local
limitoverrides any global stack trace limit (e.g., V8'sError.stackTraceLimit) for that error only. - Negative values throw a RangeError;
limitmust be a non-negative integer,undefined, orInfinity. - Conceptually, this behaves like temporarily setting the V8 specific
Error.stackTraceLimitimmediately before creating the error and restoring it right after, but without any global side effects.
This provides a standardized, cross-engine way to control stack depth per error instance and avoids manipulating a global knob that affects unrelated errors.
new Error(message, { limit: 3 })
new TypeError(message, { limit: 0 })
new RangeError(message, { limit: 50 })
new AggregateError(iterable, message, { limit: Infinity })
// ... applies to all built-in Error constructors that accept an options bag- If
optionsis provided and has alimitproperty whose value is notundefined, the engine computesn = ToIntegerOrInfinity(limit).- If
n < 0, throw aRangeError. - Otherwise, record
non the error instance.
- If
- If
limitisundefinedor absent, the default behavior is unchanged (the global limit, if any, applies).
- The global
Error.stackTraceLimitdefined in V8 is coarse-grained and impacts all errors, including those that do not need deeper stacks. - A per-error
limitexpresses intent exactly where it matters and avoids surprising global side effects. - The local limit always overrides the global limit for that specific error instance, which matches developer expectations when tailoring diagnostics.
- Eliminates boilerplate patterns that temporarily modify the global
Error.stackTraceLimitjust to construct one error.
- Changes are process-wide and time-based, so unrelated errors can inherit the wrong limit.
- Code between “set” and “reset” can throw synchronously, skipping the reset.
- Asynchronous boundaries make it easy to affect other in-flight work during
await. - People sometimes simply forget to reset the global knob.
Examples:
- Synchronous throw during error creation skips reset
// Global mutation that never resets if message coercion throws
const prev = Error.stackTraceLimit;
Error.stackTraceLimit = 2;
// toString can throw during Error(message) coercion
const msg = { toString() { throw new TypeError('format failed'); } };
new Error(msg); // throws before any reset runs; global now stuck at 2With a local limit, there’s no global mutation:
throw new Error('boom', { limit: 2 });- Asynchronous hazard: unrelated errors use your temporary limit
async function makeErrorLater() {
const prev = Error.stackTraceLimit;
Error.stackTraceLimit = Infinity; // intend: deep trace for this one error
// Meanwhile, other tasks executing during this await will also see Infinity.
await doWorkElsewhere();
const err = new Error('x'); // gets deep trace...
Error.stackTraceLimit = prev; // ...but other errors during await did too.
}With a local limit, only the intended error is affected:
async function makeErrorLater() {
await doWorkElsewhere();
throw new Error('x', { limit: Infinity });
}- Simple omission: forgetting to reset
Error.stackTraceLimit = 1;
// ... time passes, code evolves ...
// reset was forgotten; all subsequent errors now have too-short stacksPer-error limit avoids this entire class of mistakes.
- Performance-sensitive hot paths where only the first few frames are needed (e.g.,
limit: 2) to minimize overhead. - Focused deep debugging for specific error types or code paths (e.g.,
limit: 100orInfinity) without changing behavior for other errors. - Library and framework utilities that want predictable stack depth for their own diagnostic errors without affecting user code globally.
Developers frequently simulate per-error control by temporarily changing the global Error.stackTraceLimit before creating an error and resetting it afterward. Examples and references:
- MDN:
Error.stackTraceLimit(documents temporary adjustment patterns) —https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stackTraceLimit - Node.js documentation: Errors —
https://nodejs.org/api/errors.html - GitHub code search: “Error.stackTraceLimit = Infinity” —
https://github.com/search?q=%22Error.stackTraceLimit+%3D+Infinity%22&type=code - longjohn (async stack tracing) sets very high/Infinity limits —
https://github.com/mattinsler/longjohn - Mocha full trace option (ecosystem precedent for “full stacks”) —
https://mochajs.org/#--full-trace
These demonstrate that developers already rely on per-error or context-local stack depth control; standardizing a per-error limit removes the need for global mutations.
- Let
Lbe the recordedlimitfor the error instance, if provided; otherwiseundefined. - When the host captures the stack for the error:
- If
Lisundefined, apply the host’s default/global stack trace limit. - Otherwise, apply
Las the limit for this error only.
- If
- Formatting and any other host-defined stack processing are unchanged.
A stack frame is implementation defined.
- In each
Error(and subclass) constructor that accepts an options argument:- Let
optionsbe the second (or third, forAggregateError) argument. - If
optionsis notundefined:- Let
limitbe ? Get(options, "limit"). - If
limitis notundefined:- Let
nbe ? ToIntegerOrInfinity(limit). - If
n < 0, throw aRangeError. - Record
non the error instance in an internal slot for later use during stack capture.
- Let
- Let
- Let
- When capturing the stack for an error instance
E:- Let
Sbe the sequence of frames that would be captured forEabsent this option. - Let
nbeE.[[StackTraceLimitOverride]], if present; otherwiseundefined. - Apply the stack length limit:
- If
nisundefined, apply the host’s global/default limit. - Otherwise, apply
ntoS.
- If
- Produce the stack string from
Sas usual.
- Let
See the full spec draft:
- This proposal does not standardize stack string formatting, source map application, or async stack joining.
- If
limitis0, no frames are included for the error beyond the error header (implementation defined). - The option is per-error and does not affect other errors or future stack captures.
- Continue using global
Error.stackTraceLimit. Rejected for lack of precision, surprising global side effects, and boilerplate required to simulate per-error control. - Add new host-specific APIs. Rejected; standardizing a language option is more portable and interoperable.
limit provides an explicit, local way to control stack depth per error, matching developer expectations, improving ergonomics, and avoiding global side effects while aligning with ecosystem practice.