DebuggingGENERALTEAM-SPECIFICILLUSTRATIVE

Reading the Error Message

An error message answers four questions if you let it: what failed, where, with what input, and which assumption broke. Searching the exact string first skips all four and hands your hypothesis to whoever wrote the top result.

The moveWorked exampleNext questions

The situation, the reflex, and why it stalls

Every lesson starts where being stuck starts: someone has a problem, and the first move that comes to mind feels like progress.

The question

A stack trace is on the screen. What do you read before you search for it, and what does each part tell you?

The situation

The backend log for the failing checkout shows TypeError: Cannot read properties of undefined (reading 'id') with a stack trace through the handler, the ORM and the framework. Your cursor is already in the search bar with the message pasted.

The reflex

Search the exact message. Someone has seen it before; the top result has a fix; the fix is a copy-paste away. It feels like the fastest route and sometimes it is.

Why it stalls

The message is generic. Every property read on an undefined value produces it, in every codebase, for every cause; the search returns a thousand fixes for a thousand bugs, none of them yours.

What the reflex produces — and fails to produce
  • The message is generic. Every property read on an undefined value produces it, in every codebase, for every cause; the search returns a thousand fixes for a thousand bugs, none of them yours.
  • The top result proposes a cause and you adopt it. Now you are testing a stranger's hypothesis about a different system, and it is often the wrong one wearing the right words.
  • The parts of the message that are specific — the property name, the file, the line, the frame that belongs to your code — go unread, because the search bar took them as one opaque string.
  • When the search fails, there is nothing to fall back on, because the reading that would have produced a hypothesis was skipped.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

Precisely enough to apply it to a problem you have never seen — not a slogan.

  • Read the message as a structured answer before treating it as a search key. What failed: the operation that could not be completed. Where: the first frame in the trace that is in your code, not the framework's. With what input: the value that was wrong — here, something that was undefined. Which assumption: what the code at that line believed about its input that turned out to be false.
  • Then sharpen the assumption into a question — Unknown to Specific Question applied to a stack trace. "cart.items[0].id is undefined" becomes "why does this handler receive a cart whose items have no id, when it assumes they do?" That question has an experiment; the message alone did not.
  • Search after, and search for the specific part: the library and the operation, or the assumption, not the generic message. The result is now something to check against your hypothesis, not something to adopt in place of one.
  • For an error from a library or a provider, read its documentation for that error code before reading a forum about it; the documentation says what the library meant, and the forum says what someone guessed (Reading Documentation With a Goal).

From the message to a question

The same failure, asked three ways. The vague form is what gets pasted into a search bar; the best form is what gets answered by one log line and a diff. The difference between them is entirely produced by reading the message.

The undefined product
vagueWhy do I get "cannot read properties of undefined"?
betterWhy is item.product undefined in buildOrderLines for this checkout?
bestWhich code path produces a cart item without a loaded product, given that the handler assumes every item has one — and did that path change in last night's cart refactor?

why The best form names the value, the assumption and a candidate cause with a diff to read. It can be answered by logging the cart at the handler entry and reading one commit. The vague form can only be answered by a stranger describing a different bug.

Reading the trace against the four questions

A trace has more structure than a search bar sees. The table walks one, top to bottom, and says what each part answers. Most of the framework frames answer "where was the problem detected"; only the frame in your code answers "where was the assumption made".

Part of the messageWhat it answersIn the example
Error type and textWhat failedA property read on undefined — a missing value, not a wrong one
The property or operation namedOn which valueid — so the thing missing is whatever .id was read from
Framework and library framesWhere the failure was detectedORM serialisation, router — they received a bad result, they did not make it
First frame in your codeWhere the assumption livescheckout/pay.ts:41, buildOrderLines: assumes each item carries a product
"Caused by" chain, error codesThe underlying failure when wrappedAbsent here; present for provider and database errors, and usually the most specific line
Request id, timestampWhich request, so the log can be filteredr-8c1f, which links this trace to the cart logged at handler entry

Errors that are not bugs

Not every error message reports a defect. Some report the system working as designed — a decline, a validation rejection, a timeout on a provider that is genuinely down. The table separates them, because the next question is different for each: a bug wants a cause, a decline wants a product decision, a timeout wants a policy.

What the message is telling you
TriggerSymptomCauseResponse
TypeError / null reference in your frame500 to the client, "Payment failed" in the UIAn assumption about input shape broke — usually after a change upstreamFind where the input was produced; fix the producer or the assumption, not the line.
Provider returns a decline code402 with a reason; the customer sees a failureThe provider did its job; nothing in our system is wrongDecide what the customer sees and what happens to the order (What If Payment Fails?).
Validation error from our own API400 with a field nameThe frontend sent something the contract forbidsRead the contract; the bug is in the client or in the contract, not in the validator (Validation Errors: Feedback, Not Verdicts).
Timeout on the provider callSlow failure; sometimes the charge happened anywayThe provider is slow or down; the outcome is unknown, not failedTreat as ambiguous, not as failure; the next question is about idempotent retry (A Timeout Tells You Nothing About Whether It Happened).
Deadlock or constraint error from the databaseError after the provider succeededTwo requests contended for the same rows, or an invariant heldThe constraint may be correct and the duplicate request the bug (Duplicate Requests).

How to do it

Most important first.

  • Read the whole message, including the parts after the first line: error codes, the property name, the "caused by" chain. Most people stop at the first line.
  • Walk the trace from the top until the first frame in your own code. That is where the assumption lives; frames above it are the library discovering the problem, not causing it.
  • Name the value: what exactly was undefined, null, missing or malformed? If the message does not say, the first experiment is to print or inspect that value at that frame.
  • Write the broken assumption as a sentence, then as a question with an experiment (Question Quality).
  • Search with the specific parts — library name, operation, error code — and read results as candidate hypotheses to test, not as fixes to apply (Evaluating What the Search Returned).

Worked on a concrete problem

The move has to produce something. This is what it produced.

  • The message: TypeError: Cannot read properties of undefined (reading 'id'). What failed: reading .id. Where: the trace has six frames in the framework and ORM and then checkout/pay.ts:41, in buildOrderLines. With what input: at line 41, item.product.id — so item.product is undefined for some item. Which assumption: every cart item has a loaded product. Question: why does a cart item arrive without its product? Experiment: log the cart at the top of the handler for the failing request id. The cart refactor last night changed items from a list of objects with products to a map keyed by product id; the handler was never updated. Nobody's search result could have said this.
  • A provider error: card_declined: insufficient_funds with an HTTP 402. What failed: the charge. Where: the provider, not us. With what input: a specific card. Which assumption: none broken — this is the provider doing its job. The question becomes a product one: what should the customer see, and should the order stay open? Searching the message would have led to a page about test card numbers.
  • The "google the error" slogan, made falsifiable: it helps when the message is specific to a library or an environment — an error code, a version conflict, a misconfiguration — and it fails when the message is generic to the language. The property name and the frame decide which kind you have, and you only know them by reading first.

How you know it worked

What now exists that did not before, and what question you can now ask.

  • You can say, for the error on screen, what failed, in which of your frames, on which value, and what assumption broke.
  • You have a question with an experiment before you have opened a search tab.
  • Search results are being checked against your hypothesis rather than replacing it.
  • The fix, when it comes, addresses the assumption, not the symptom — the cart shape, not a null check at line 41.

The questions you can now ask

The field this whole domain exists for. After this lesson, these are the questions to put to an unfamiliar problem.

Next questions
  • ?What operation failed, in which frame of my own code, on which value?
  • ?What did the code at that line assume about its input, and where did the input come from?
  • ?Is this error mine, the library's, or the provider's doing its job — and does each need a different next question?
  • ?What specific part of this message is worth searching, and what would a useful result have to tell me?

What can go wrong

How the move itself fails
  • Reading the trace and stopping at the first frame in your code when the cause is upstream: the frame shows where the assumption broke, and the value was produced somewhere earlier (Follow the Data).
  • Papering over the assumption. A null check at the failing line makes the message go away and lets the malformed cart continue to the provider; the assumption was correct and the input was wrong.
  • Never searching. For a library error code, the documentation and a good search result are the fastest route to what the library meant; refusing them is delegation avoidance turned into slowness.
  • Reading a message from the wrong layer. The frontend's "Payment failed" is not the error; the backend's 500 is not the error either; the error is the exception that produced the 500, and it may be three layers down (Zoom In, Zoom Out).
What the move costs
  • Reading takes longer than pasting, and for a well-known environment error — a missing dependency, a port in use — the paste wins and the reading is redundant.
  • Stack traces in some runtimes and frameworks are deep and mangled; finding the first frame of your own code can take effort, and source maps or symbolication may be needed before the reading is possible.
  • The assumption-as-question step produces an experiment, which is more work than a guessed fix and is the reason the fix holds.
Misreads
  • "Never search error messages." Search after reading, and search for the specific part. The domain is pro-tool; the objection is to letting the search bar do the reading.
  • "The line in the trace is the bug." It is where the assumption was detected. The bug is wherever the input was produced, which can be a different module, a different service, or a data migration.
  • "Framework frames are noise." They are context: the frame where a library validated your input and rejected it says what the library expected, which is often the assumption you broke.

Where this applies

Problem-solving advice is stated as universal far more often than it is. These labels say what each method is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.

  • GENERALStack traces, compiler errors, HTTP error bodies, provider error codes and database errors all answer the same four questions; the fields are in different places.
  • TEAM-SPECIFICFor a learner, reading first is the whole point — the manifesto's "debug without AI" starts here. For a senior on a deadline, the reading is a two-second glance and the search follows immediately; the order is the same and the visible time differs.
  • ILLUSTRATIVEThe TypeError, the frame at line 41 and the cart-as-map refactor are invented to show the reading producing a question; no real trace is quoted.

Where the depth lives

This domain asks the question and hands the answer off by name.

Distributed Systemstimeout-ambiguity
Further
  • A Programming Languages & Runtime Internals domain does not exist yet; how a runtime builds a stack trace, what a source map restores, and why some frames are missing belong there.