noctcore-contracts/require-error-cause
Re-throwing inside a
catchwithout{ cause }severs the chain to the original error. 🔧
Recommended preset: error · Autofix: yes · Suggestions: no · Type information: not needed
When you catch an error and throw a new one, the new error is what reaches your logger. If you do not
forward the caught error as cause, the underlying failure — its message, stack, and any nested
cause — is gone. The stack you see points only at the re-throw site. Error’s standard cause
option (and every *Error subclass that forwards it) preserves the chain:
try { await db.query(sql);} catch (err) { throw new QueryError('load failed', { cause: err }); // chain intact}What it flags
Section titled “What it flags”A throw new SomeError(...) inside a catch block when no argument carries a cause property.
A throw nested in a closure declared inside the catch is still flagged: the binding is genuinely in
scope there. Nested try/catch uses the nearest binding.
// drops the cause: autofixes to `new Error('failed', { cause: err })`try { work(); } catch (err) { throw new Error('failed'); }
// merges into an existing options objecttry { work(); } catch (err) { throw new HttpError('failed', { status: 500 }); }try { work(); } catch (err) { throw new Error('failed', { cause: err }); }try { work(); } catch (err) { throw err; }Autofix attaches { cause: <binding> }:
- one argument (
new Error('msg')) → appends a new options object; - an existing options object → merges
causein; - an empty options object (
{}) → fills it.
A zero-argument new SomeError() is reported but not autofixed — inserting an options object as
the first argument could clobber a positional message the rule cannot see.
What it does not flag
Section titled “What it does not flag”Deliberately conservative:
- Only constructors whose simple name ends in
ErrororExceptionare treated as errors (Error,TypeError,ValidationError,HttpException). Throwingnew Response(...)for control flow is not policed. - Fires only when the enclosing catch binds a plain identifier (
catch (err)). A parameterlesscatch {}or a destructured binding (catch ({ message })) has no single name to attach, so the throw is left alone. - Any
causeproperty (whatever its value), and any spread the rule cannot see through, counts as “has a cause” and suppresses the report — a hand-written cause is never second-guessed. - A bare re-throw of the caught error (
throw err).
When not to use it
Section titled “When not to use it”If your error classes do not accept an options object with cause (pre-ES2022 targets without a
polyfill), or you deliberately model errors without chaining, leave this rule off.