err.stack follows V8's format and the engine matches Node's output on the differential corpus (testdata/errors/cases.txt, 213 snippets; expected.json is what Node v24.22.1 printed, recorded by tools/errors-diff/record.js). The report's = stack: line and the JSON stack array come from the same captured frames, so they never disagree with err.stack.
Format
<Name>: <message> at <frame> at <frame>
- The header is
Name: message(justNamewhen the message is empty). It is formatted whenstackis first read, so anameormessageset after construction shows, as in V8. - At most
Error.stackTraceLimitframes (default 10) are kept, innermost first, captured when the error object is created. - A frame line is one of:
at fn (file:line:col)for a named function;at Type.method (file:line:col)for a method call whose receiver has a class, whereTypeis the name of the constructor the receiver inherits (String,Number,Boolean,Symbol,BigInt,Function, a class name,Objectfor a plain object unless it has aSymbol.toStringTag);at new Fn (file:line:col)for a construct call;at file:line:colfor top-level code and anonymous functions;at async fn (file:line:col)for an async function that is suspended at anawaitbehind the running frames;at Array.map (<anonymous>)for a built-in function between script frames;at eval (eval at f (file:line:col), <anonymous>:line:col)for code made byevalornew Function.
- The error constructors,
ArrayandProxyare left out of the trace of the error they create; frames of a subclass constructor are skipped up to thenew.targetof the construction. - Columns count UTF-16 code units, 1-based.
Positions
V8 reports a position inside the expression, not the start of the statement:
| Construct | Position |
|---|---|
property read or method call a.b / a.b() | the property name b |
call f() | the start of the callee f |
call of another expression (x)(), a()() | the operator that follows the callee, skipping ) and comments |
| binary operation, assignment, compound assignment | the operator |
new C() | the new keyword |
| tagged template | the template |
yield | the start of its operand |
| destructuring a value that cannot be unpacked | the first property key of the pattern |
Each bytecode instruction carries one such position (FunctionCode.Pos); the frame's span (used for the report's underline) is the span of the failing expression and is kept separately.
Frames of a report
- Built-in frames (
Array.map) appear inerr.stackbut not in the report; the report points at the script code that called them. - A repeated frame (same function, source and line, consecutive) collapses to
fn (file:line) ×Nin the report line;err.stackand the JSON keep every frame. - Top-level code is
<top-level>in the report line and hasIsTopLevelset; an anonymous function is<anonymous>. - Async frames: when an async function is suspended at an
awaitof a promise made by another async function, the awaiting function is stitched behind the bottom frame (async main). Only direct awaits are stitched. - Proper tail calls are off by default so the caller stays in the trace (as in V8);
ScriptEngineOptions.ProperTailCallsturns them on (the test262 runner does).
Differences from Node (documented, testdata/errors/documented.txt)
- The frame at the call-depth limit differs: V8's limit depends on native stack left, ours is a fixed depth (10,000).
- A derived constructor that returns without calling
super(): V8 points at the parameter list, the engine at the body. - Class field initializers run inline (frame named after the field); V8 shows a synthetic
<instance_members_initializer>frame. new Functionwith a parameter list that closes early: V8's message is "Arg string terminates parameters early".await Promise.all(...): V8 stitches anasync Promise.all (index n)frame; the engine does not.- A
for awaitloop is reported at its binding by V8, at theforstatement by the engine. - Assigning to
undefinedin strict code names the receiver[object Object]in V8's vm context,#<Object>in the engine.