hint-errors v1.3.0

for node.js

Read the error, not the stack trace.

hint-errors catches uncaught exceptions and unhandled rejections, then prints what broke, where it happened, and what to check next, instead of forty lines of V8 you have to decode by hand.

$npm install hint-errors

Before and after hint-errors

what one require() changes

before: raw V8 trace
/app/src/user.js:12
console.log(user.name);
                 ^

TypeError: Cannot read properties of
undefined (reading 'name')
    at Object.<anonymous>
      (/app/src/user.js:12:18)
    at Module._compile
      (node:internal/modules/cjs/loader:1105:14)
    ...
after: hint-errors
error
TypeError
message
Cannot read properties of undefined (reading 'name')
location
src/user.js: line 12
hint
You're trying to access a property on something that doesn't exist yet. Check that the value is defined before you use it — a quick console.log just above the error line will show you what it actually is.

Getting Started

Three steps. Which entry point you require depends on whether the process entry point should be used, with safe termination after an uncaught error.

terminal
npm install hint-errors
index.js
require("hint-errors");

// the rest of your entry file, unchanged

Run your script. The first uncaught exception or unhandled rejection now prints a hint block instead of a raw trace, and process.exitCode is set to 1.

hint-errors is a development aid. It disables itself automatically when NODE_ENV=production. See Production Safety for what that means and how to override it.

How It Works

Registering a listener on Node's uncaughtException or unhandledRejection event hands control to hint-errors. Node stops printing its own trace and deciding whether to exit, and that responsibility moves to the listener. A four-stage pipeline runs on every error, in this order.

Two entry points cover two different runtime shapes:

require("hint-errors")
Scripts, CLIs, short-lived processes. Prints the hint, sets process.exitCode = 1, then lets the process end on its own.
require("hint-errors/server")
Compatibility entry point for long-running processes such as HTTP servers. Prints the hint, flushes stdout, and exits with code 1 so an external supervisor can restart the process.

Usage

The same pipeline, wired to whichever kind of process you're running.

Scripts & CLIs

The default entry point. Best for one-off scripts, build tools, and CLIs where exiting on failure is the correct behavior.

index.js
require("hint-errors");

run();

On an uncaught error, it prints the hint block, sets process.exitCode = 1, and lets the event loop drain. There's no forced process.exit(), so buffered stdout isn't dropped when the output is piped.

Servers

Use the server entry point when a long-running process needs the same formatted diagnostics while still terminating safely after an uncaught failure.

server.js
require("hint-errors/server");

const http = require("http");

const server = http.createServer((req, res) => {
  if (req.url === "/crash") {
    const user = undefined;
    console.log(user.name); // hint prints, then exits after flushing
  }
  res.end("ok");
});

server.listen(3000);

The formatted diagnostic is flushed before the process exits, so a process supervisor can restart the server with the complete hint instead of leaving a potentially corrupted process alive.

ESM

Thin .mjs shims (index.mjs, server.mjs) re-export the same CommonJS implementation through package.json's conditional exports map. Node evaluates the same underlying module either way, so there's no separate logic to drift out of sync.

index.mjs
import "hint-errors";
import { addHint } from "hint-errors";

Same for hint-errors/server: import "hint-errors/server" works directly, no createRequire() workaround needed in a "type": "module" project.

Custom Hints

Before v1.3.0, a team's own domain-specific errors (a custom OrderValidationError, an internal error code) had no supported way to get a hint without forking the package.

addHint() is exported from both entry points and registers a { match, hint } entry at runtime, in exactly the shape the built-in list already uses.

index.js
const { addHint } = require("hint-errors");

class OrderValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "OrderValidationError";
  }
}

addHint({
  match: "OrderValidationError",
  hint: `An order failed validation before it reached payment.
Check the cart contents against current stock and pricing rules
before retrying checkout.`,
});

require("hint-errors");

Match order

Matching stops at the first hit, so by default a custom hint is checked before every built-in entry. Your own domain errors win over a generic pattern that might otherwise match first. Pass { priority: "low" } to invert that: the custom hint is only tried after every built-in has failed to match, which is useful for a catch-all fallback that shouldn't shadow anything more specific.

index.js
addHint(
  { match: /timeout/i, hint: "Check the upstream service's health dashboard." },
  { priority: "low" }
);

Passing an invalid entry (missing match or hint, the wrong type, or an unrecognized priority) throws a TypeError immediately instead of failing silently later.

API Reference

Everything hint-errors exposes. If it isn't listed here, it isn't part of the public API.

Entry points

hint-errors
Registers the pipeline for scripts and short-lived processes; exits via process.exitCode after an unhandled error. Exports { addHint }.
hint-errors/server
Same pipeline with safe termination after the formatted output is flushed. This compatibility entry point also exports { addHint }.

Functions

addHint({ match, hint }, options?)
exported from both entry points Registers an additional hint entry at runtime. match is a string or a RegExp tested against "ErrorType: message"; hint is the string shown when it matches. options.priority defaults to "high" (checked before every built-in); set to "low" to check it only after every built-in has been tried. Throws a TypeError for a missing or malformed entry.

Environment variables

NODE_ENV
string · default: unsetWhen set to "production", hint-errors registers no listeners at all and has zero effect on the process.
HINT_ERRORS_FORCE
"1" or "true" · default: unsetOverrides the production check so hint-errors stays active even with NODE_ENV=production.
NO_COLOR
presence-based · default: unsetDisables ANSI color codes in the output, following the cross-tool NO_COLOR convention. Takes precedence over FORCE_COLOR.
FORCE_COLOR
presence-based · default: unsetForces ANSI color output even when stdout isn't an interactive TTY, which is useful for CI logs that render color.
TERM
string · default: inherited from shellWhen set to "dumb", color output is disabled, matching how most terminal tooling treats that value.

Error Gallery

Every pattern below is a real entry from src/hints.js. The full list covers 40+ cases across JavaScript errors, Node/OS errors, and network errors. Anything that doesn't match still gets a generic, actionable fallback instead of nothing.

Try it

Production Safety

hint-errors is built to disappear the moment it isn't wanted.

Self-disabling
Both entry points check NODE_ENV on load. When it's "production", no listeners are registered, so the package has zero effect on the process. A A colored notice explains why. Set HINT_ERRORS_FORCE=1 if you genuinely want it active anyway.
Why it matters for servers
The server entry point now terminates after flushing its diagnostic. Node's own docs describe the process as being in a potentially undefined state after an uncaught exception, so an external supervisor can restart it safely.
Coexists with monitoring tools
On load, hint-errors snapshots any uncaughtException/unhandledRejection listeners already registered, removes them, installs its own listener first, then re-invokes the originals with the original error. Nothing is dropped or replaced. Only the order changes, so Sentry, Winston, or an APM agent still runs, just after the hint prints.

Limitations

What's intentionally not solved yet, stated plainly.

  • Source-map resolution Bundled or minified production code (webpack, esbuild, Vite output) will report the location in the built file, not the original source. This is a documented limitation rather than a line number that's silently wrong.
  • No published web playground The "try it" widget on this page runs against a small mirrored dataset in the browser, not a live Node process. A real hosted playground is planned for a future phase.
  • Manual steps Pushing a version tag with git push --follow-tags creates the GitHub Release automatically. Only publishing to npm is a manual step — run npm publish after the tag is up.