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:
- Sort by time, not by severity.
ERRORis 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. - 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.
- 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.
Added 2026-09-11 (run 12): before triaging frames, consider shrinking the failure — skills/minimal-reproduction varies one variable at a time until the error flips, which often finds the first bad frame faster than reading.