Skip to content

Latest commit

 

History

History
70 lines (58 loc) · 4.6 KB

File metadata and controls

70 lines (58 loc) · 4.6 KB
id debugging-signals-stack-traces
domain debugging
category signals
applies_to
general
confidence verified
sources
last_verified 2026-07-10
related
debugging-methodology-reproduce-first
debugging-signals-logs-and-correlation

Reading an Exception Stack Trace for the Root Cause

When this applies

You have an exception, crash, or stack trace to interpret — from a log, a test failure, an error tracker, or a terminal — and you need to decide where the fault actually is.

Do this

  1. Read the deepest Caused by: / cause in the chain first. Frameworks wrap low-level exceptions in layers of generic ones; the top of the printout is the last wrapper, the bottom-most cause is the original failure. Diagnose from the root cause's type, message, and frames.
  2. Within the root cause's frames, find the first frame in your code (your package/module path), scanning top-down past framework and library frames. That frame is where your code met the failure; frames above it are machinery.
  3. Read the exception message before the frames — it carries the failing value (which key was missing, which column, what was null/expected). Frames tell you where; the message tells you what.
  4. Match the trace to the exact source version that produced it: resolve the build/commit from the log or artifact and read that revision's line numbers. Line numbers against a different revision point at the wrong statements.
  5. For async code (async/await, promises, executors, message handlers), the trace starts at the scheduler, not at your call site. Get the origin:
Case Do
Node.js / V8 with async/await await-based code gets async frames in Error#stack (zero-cost async stack traces); prefer async/await over raw promise chains to keep origins visible
Browser JS Debug with DevTools async stack traces enabled; for production errors, symbolicate first (source maps, row below)
JVM thread pools / executors The worker thread's trace bottoms out in the pool. Find the scheduling site: search for what submitted the task (wrap submission to capture a creation-site trace when this recurs)
Rethrown exception with the cause dropped (throw new X(...) without the original) The origin is gone from the trace. Reproduce with a breakpoint/log at the rethrow site to capture the original exception; fix the rethrow to chain the cause

Edge cases

Case Then
Trace is generic (RuntimeException: error) with your handler on top Someone caught the real exception and rethrew generically. Go to the catch site in the trace and log/inspect what it swallowed
NPE (or TypeError: cannot read property of undefined) points at a chained call a.b().c().d() The line does not tell you which link was null. Split the chain into one call per line (or add per-link assertions) and re-run the reproduction
Minified/bundled JS frames (chunk.4f2a.js:1:38271) Symbolicate with the build's source map (DevTools or your error tracker) before reading; unminified frame names first, then apply the steps above
Same exception type thrown from many places Match on the full frame path + message, not the type, before assuming two occurrences are the same bug
Trace appears repeatedly in logs The first occurrence is the one to diagnose; later ones can be cascade noise — per [debugging-signals-logs-and-correlation]

Instead of

If you are about to Do this instead Why
Diagnose from the top-most exception and frame Walk to the deepest cause, then to the first of your frames within it The top is the last wrapper; fixing there treats a symptom
Fix the line the NPE points at by adding a null-check on the whole expression Split the chained call and identify which link was null, then fix why it was null A blanket null-check hides the absent value and moves the failure downstream
Read minified frames "well enough" Symbolicate with source maps first Guessed mappings put you in the wrong function; every later step inherits the error

Sources