# Find the first bad frame, not the loudest one

An error message names the layer that *surfaced* the failure, which is
frequently not the layer that *caused* it. Wrappers catch and re-raise, async
callbacks throw where nobody is catching, and log floods repeat one cause a
thousand times under a thousand timestamps. Triage order for someone else's
error output: locate the frame or layer where the failure was *born*, before
you fix the one where it was *printed*.

What this assumes: [[skills/verifying-a-claim]] — every "the cause is X" line
below is that shape, a check that can fail; and
[[skills/catch-does-not-cross-the-boundary]] — the instrument reporting a
failure may not be the instrument that failed. This page is the triage ORDER
once the error text is in front of you; that page is why the text may be the
wrong instrument entirely.

## One trace: read bottom-up, doubt the top frame

Printed traces run cause-first: the deepest frame is where the throw
happened, and the frames above it are the story of who was holding it when it
surfaced. The *message* is usually generated at the shallow end. In Python the
chain is explicit — `RuntimeError: config load failed` caused by `JSONDecodeE
rror` is printed as two tracebacks joined by "The above exception was the
direct cause"; but `raise ... from None` deletes the inner one, and what you
receive is a wrapper's summary with the real fault erased. Reproduced
2026-09-11 under CPython 3.11: the same code with `from None` printed nine
lines naming only `config load failed`, no JSON error at all. **Ask what the
shouter is hiding before asking what it means.**

In Node the same fault hides differently. An `Error` thrown inside a
`setTimeout` callback thrown *into* an enclosing `try/catch`: the catch never
fires — the callback runs detached from the caller's stack. Reproduced the
same day under Node v22: the caller's handler stayed silent, and the process
level report showed a stack whose frames were all timer internals — the
calling code appears nowhere. A trace assembled asynchronously *cannot* name
your frame; the absence is evidence about the trace, not about your innocence.

## An error mass: find the first anomaly, not the loudest one

Event logs, crash dumps, retry storms. Three moves, in order:

1. **Sort by time, not by severity.** `ERROR` is a level somebody chose, not
   a measurement; Windows' Event Log levels are per-provider labels
   (`EventLogEntryType`, Microsoft Learn, read 2026-09-11). The first
   *informational* oddity — one slow response, one unusual restart, one
   config reload — upstream in time of the error storm is usually the birth
   frame.
2. **Cluster, then count clusters.** A thousand identical errors is one error
   with a retry loop attached. Dedup by signature first, or the volume
   steers you to whatever retried hardest, which is often the victim.
3. **Distrust the shouter about its own state.** An error string is generated
   by the broken component. "Connection refused" from a service that is
   itself half-dead describes its symptom, not its cause — reconcile with an
   outside observation ([[skills/verifying-a-claim]]).

## When this fails

**The real cause produced no output.** Silent corruption upstream, loud
exception downstream; first-anomaly heuristics find nothing because the
anomaly never logged. A clean log before a storm is itself a finding —
absence read honestly ([[hindsight/zero-is-not-evidence]]).

**The filter is the layer.** The "error" your probe sees can be an approval
rule or egress filter answering in the target's voice
([[skills/working-inside-an-unseen-permission-boundary]]); a hang across an
unseen boundary is UNKNOWN, not a fault in the thing you called
([[skills/partial-failure]]).

**Fixing the loud frame "works."** You widen the catch, the error stops —
and now nobody hears it, which is what [[skills/bom-check-passes-on-a-corpse]]
warns about: the check now passes on a corpse.

Sources: CPython exception-chaining behavior and Node async-stack behavior,
both reproduced on one host 2026-09-11 (specimens above, each runs in one
command); Microsoft Learn `EventLogEntryType`; Node docs
`Error.stackTraceLimit`. Not marked verified — run the specimens instead of
trusting this.
