Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gitfault

npm version npm downloads license

Enrich thrown errors with git context — who wrote the line, what changed recently, what the code around it looks like — and print it to your terminal or post it to Slack. No account, no SaaS, no data leaves your machine unless you configure a Slack webhook yourself.

The anti-Sentry. Zero runtime dependencies — the core package shells out to the git binary directly instead of pulling in a wrapper library.

Install

npm install gitfault

Requires Node 18+ and a git repository (gitfault reads git blame / git log from the file that threw — it isn't useful outside a repo).

Quick start

import { attachGlobalHandlers } from 'gitfault/attach';

attachGlobalHandlers();

That's it. Any uncaught exception or unhandled rejection now prints an enriched report to stderr before the process exits, e.g.:

Error: kaboom
  at /repo/src/orders.js:42

Blame:
  Jane Doe · a1b2c3d · fix race condition in checkout

Source:
    37 | function processOrder(order) {
    38 |   validate(order);
    39 |
  > 42 |   throw new Error('kaboom');
    41 |
    42 | }

Recent commits:
  a1b2c3d fix race condition in checkout
  9f8e7d6 add order validation

Express / Fastify middleware

import { gitfaultHandler } from 'gitfault/attach';

// Express
app.use(gitfaultHandler());

// Fastify
fastify.setErrorHandler(gitfaultHandler());

The same handler works with both frameworks — it detects which one called it (Express passes a next callback, Fastify doesn't) and either forwards the error unchanged via next(err) or sends it via reply.send(err). Either way, the original error is never swallowed or replaced — enrichment is purely observational.

Sinks

Two ship out of the box: a terminal sink (default) and a Slack sink.

import { attachGlobalHandlers, createSlackSink, terminalSink } from 'gitfault/attach';

attachGlobalHandlers({
  sinks: [terminalSink, createSlackSink({ webhookUrl: process.env.SLACK_WEBHOOK_URL })],
});

createSlackSink posts a Block Kit message (error, file:line, blame, source snippet, and a commit link if a git remote is configured) to a Slack incoming webhook. It never throws or blocks the caller — failures are logged locally with console.error and swallowed. Repeated errors from the same throw site are rate-limited to one Slack message per minute by default.

createSlackSink({
  webhookUrl: 'https://hooks.slack.com/services/...', // or set GITFAULT_SLACK_WEBHOOK_URL
  rateLimitMs: 60_000, // 0 disables rate limiting
});

Write your own sink by matching the shape (enriched: EnrichedError) => void.

Configuration

Both attachGlobalHandlers and gitfaultHandler accept an EnrichConfig:

Option Default Description
cwd process.cwd() Directory to run git commands in
contextLines 5 Lines of source shown above/below the throw site
commitCount 5 Number of recent commits to fetch for the file
timeoutMs 2000 Kill a git subprocess after this many ms
ignore [] Strings (substring match) or RegExps — files matching skip git/source lookups entirely

Plus, on attachGlobalHandlers only:

Option Default Description
sinks [terminalSink] Where enriched errors are sent
exitOnUncaughtException true Call process.exit(1) after handling an uncaught exception, matching Node's default crash behavior
exitOnUnhandledRejection true Same, for unhandled rejections

Using the enrichment pipeline directly

Everything above is built on one pure function — no I/O side effects beyond the git/fs calls it explicitly makes, and it never throws:

import { enrich } from 'gitfault';

try {
  riskyThing();
} catch (error) {
  const enriched = enrich(error);
  console.log(enriched.blame, enriched.source, enriched.recentCommits);
}

enrich() returns an EnrichedError:

interface EnrichedError {
  name: string;
  message: string;
  stack: string | undefined;
  frames: StackFrame[];       // every parsed frame in the stack
  throwSite: StackFrame | null; // best guess at the frame that threw
  ignored: boolean;           // true if throwSite matched an `ignore` pattern
  blame: BlameInfo | null;
  recentCommits: CommitLogEntry[];
  source: SourceSnippet | null;
}

The lower-level pieces are exported too, in case you want to compose your own pipeline: parseStack, getBlame, getRecentCommits, getSourceSnippet, formatEnrichedError, buildSlackMessage.

Design notes

  • Zero runtime dependencies. git is invoked directly via node:child_process; formatting uses hand-rolled ANSI codes, not chalk/boxen.
  • Never crashes your app. Every git/fs lookup degrades to null/[] on failure — not in a git repo, file untracked, git binary missing, subprocess timeout, sink throwing — enrichment is best-effort.
  • Stateless. No error grouping or deduplication across process restarts; the Slack rate limiter's memory lives only as long as the sink instance.

License

MIT

About

Runtime error enrichment with git blame, commit context, and source windows for Node/TS apps

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages