noctcore-observability/no-error-detail-loss
When a catch block reports a failure, log the error itself — not just its message.
Recommended preset: error · Autofix: no · Suggestions: no · Type information: not needed
A caught error carries a stack trace and often a cause — the parts you actually need to
debug a production failure. A catch block that logs only e.message, String(e), or `${e}`
throws that away: the log records that something failed but not where or why.
// stack and cause are gonetry { await run(); } catch (e) { logger.error(`run failed: ${e.message}`);}// the whole error survivestry { await run(); } catch (e) { logger.error('run failed', { err: e });}What it flags
Section titled “What it flags”A catch (e) block where all of the following hold:
- the catch binding is a plain identifier (
catch (e)); - the block contains a logger call (so a failure is actually being reported);
- the binding is referenced (an unused binding is a different concern); and
- every reference to the binding is a lossy form —
e.message,String(e), or bare`${e}`.
What it does not flag
Section titled “What it does not flag”If the error is passed whole anywhere (logger.error('x', e), { err: e }), or .stack / .cause /
any other property is read, or it is re-thrown, its diagnostics survive and the rule stays silent:
// .stack is read: not a lossy formtry { await run(); } catch (e) { logger.error(`failed: ${e.stack}`); }
// mixed use: the full capture winstry { await run(); } catch (e) { logger.error(`${e.message}`, { err: e }); }Distinct from a fully-unused catch binding (catch (e) { cleanup(); }), which this rule
deliberately does not touch.
A catch block with no logger call at all (catch (e) { throw e; }) is not reporting a failure, so it
is not checked.
When not to use it
Section titled “When not to use it”If you deliberately log only messages (e.g. to a user-facing channel that must not include stack traces) and capture the full error elsewhere, this rule will be noisy — scope it off for those files.