@misaka-net/fatal-guard
v0.2.2
Published
Zero-dependency non-invasive fatal error guard — capture uncaught exceptions and unhandled rejections, route structured 4-field payload to external handler via process.env.FATAL_HANDLER
Maintainers
Readme
@misaka-net/fatal-guard
Zero-dependency non-invasive fatal error guard for Node.js CLIs.
Capture uncaught exceptions, unhandled rejections, and non-zero exits — route a structured 4-field payload to any external handler.
npm i @misaka-net/fatal-guard
FATAL_HANDLER=/usr/bin/logger node -r @misaka-net/fatal-guard/register ./app.jsOne env var. No source code changes.
Quick start
Wrapper mode (no code changes needed)
# Wrap any Node.js CLI — automatically detects crashes via stderr + exit code
FATAL_HANDLER=/usr/bin/logger npx @misaka-net/fatal-guard -- node app.js
# Works with any CLI tool — Vite, E2B, OpenClaw, tsdown, etc.
FATAL_HANDLER=/usr/bin/logger npx @misaka-net/fatal-guard -- ./node_modules/.bin/vite buildPreload mode (node -r)
npm i @misaka-net/fatal-guard
FATAL_HANDLER=/usr/bin/logger node -r @misaka-net/fatal-guard/register ./app.jsBoth modes produce identical 4-field payloads in syslog on crash.
# 1. Install
npm i @misaka-net/fatal-guard
# 2. Run any Node.js CLI with the guard preloaded
FATAL_HANDLER=/usr/bin/logger node -r @misaka-net/fatal-guard/register ./your-cli.js
# 3. Trigger a fatal error — watch syslog light up
# (uncaught exceptions, unhandled rejections, non-zero exits all captured)Real syslog output after an uncaught exception:
Jun 19 08:38:26 hostname logger[180739]:
{"schemaVersion":1,"reason":"uncaught_exception",
"timestamp":"2026-06-19T08:38:26.091Z","pid":180739}
Jun 19 08:38:26 hostname logger[180739]:
{"schemaVersion":1,"reason":"exit_code",
"timestamp":"2026-06-19T08:38:26.096Z","pid":180739}How it works
register.js hooks into three process-level fatal signals:
| Signal | Trigger | Behavior |
|--------|---------|----------|
| uncaughtException | Synchronous throw not caught by any try/catch | Fires handler, prints to stderr, exits with code 1 |
| unhandledRejection | Async promise rejection with no .catch() | Fires handler, prints warning |
| exit_code / SIGTERM / SIGINT | Non-zero exit or termination signal | Fires handler |
On each signal, the handler executable is spawned with a single JSON argv argument:
{
"schemaVersion": 1,
"reason": "uncaught_exception",
"timestamp": "2026-06-19T04:20:00.000Z",
"pid": 12345
}4 fields only. No stack traces, no environment variables, no secrets. Designed to be redacted by default — the handler decides what to enrich.
Handler examples
| Handler | Command | Effect |
|---------|---------|--------|
| syslog | FATAL_HANDLER=/usr/bin/logger | Writes to systemd journal / syslog |
| HTTP webhook | FATAL_HANDLER=/usr/bin/curl | Receives JSON as argv[1] — pair with a small wrapper script for HTTP POST |
| Custom script | FATAL_HANDLER=/opt/alert-to-slack.sh | Write your own alert pipeline |
Design principles
| Principle | Implementation |
|-----------|---------------|
| Zero dependencies | require('node:child_process') only |
| Non-blocking | spawn + detached: true + unref() — handler never blocks shutdown |
| Injection-safe | shell: false — no shell interpretation of env var or payload |
| Fire-and-forget | Handler failure is silently swallowed — process continues |
| Redacted by default | 4 fields only — no stack, no env vars, no secrets |
API
const { buildPayload, runHandler } = require('@misaka-net/fatal-guard');
buildPayload('uncaught_exception');
// → '{"schemaVersion":1,"reason":"uncaught_exception","timestamp":"...","pid":12345}'
runHandler('uncaught_exception');
// → spawns FATAL_HANDLER with payload as argv[1], or no-op if FATAL_HANDLER is unsetRelated
- GitHub
- npm:
@misaka-net/fatal-guard
