The Bug That Doesn't Crash Anything

The failure worth catching never turns red

8 min read

Argued into existence in the Writing Room10 messages · 2 technical catches · 2 minds changed
The Bug That Doesn't Crash Anything

Let's open the hood on the failure nobody warns you about, because it doesn't announce itself.

You know the loud kind: something goes red, the page won't load, a wall of text appears. That one has a fix path — we walked it in Read Error Messages Like a Detective. This is the other kind: your project runs, does roughly what you asked, and is wrong in a place you'd only find by reading it.

Stack Overflow's 2025 developer survey asked what actually bites people about AI-generated code: 66% named "almost right, but not quite" as a problem they hit. 45.2% said debugging AI-generated code takes longer than debugging their own. And trust in these tools' accuracy fell ten points year on year, from 43% to 33% — the headline AI-section figure in the 2025 results, at survey.stackoverflow.co/2025/ai.

Those three numbers describe a failure with no traceback. Nothing stops, nothing turns red. The only instrument that catches it is a person reading the file.

One file, four passes, no rush. You did what the tutorial said, and it was right — this is the next thing, not a correction of the last one.

The file should be yours

Open the project you actually built — the one that works, that you couldn't fully explain if someone asked — and pick the file with the most code in it.

I'll work alongside you on one of the same shape: a to-do list with priorities, what any assistant hands you when you ask for one.

<!doctype html>
<html>
  <body>
    <h1>My To-Do App</h1>
    <input id="task-input" placeholder="New task" />
    <input id="priority-input" type="number" value="3" min="1" max="5" />
    <button id="add-btn">Add</button>
    <ul id="task-list"></ul>

    <script>
      const STORAGE_KEY = "tasks";

      function loadTasks() {
        const raw = localStorage.getItem(STORAGE_KEY);
        return raw ? JSON.parse(raw) : [];
      }

      function saveTasks(tasks) {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
      }

      function render() {
        const tasks = loadTasks();
        const ordered = [...tasks].sort((a, b) => a.priority - b.priority);
        const list = document.getElementById("task-list");
        list.innerHTML = "";
        ordered.forEach(function (task) {
          const li = document.createElement("li");
          li.textContent = task.priority + " - " + task.name;
          list.appendChild(li);
        });
      }

      document.getElementById("add-btn").addEventListener("click", function () {
        const input = document.getElementById("task-input");
        const priority = document.getElementById("priority-input");
        const name = input.value.trim();
        if (!name) return;
        const tasks = loadTasks();
        tasks.push({ name: name, priority: Number(priority.value) });
        saveTasks(tasks);
        input.value = "";
        render();
      });

      render();
    </script>
  </body>
</html>

Save it as 'tasks.html', open it in a browser, and add three tasks deliberately out of priority order: 'buy milk' at 3, then 'call mum' at 1, then 'pay rent' at 2. That order matters for pass three below.

Every screen result below came from this page in a browser on 24 July 2026, reproduced independently in jsdom 27.1.1 on Node 22.22.2 by our QC editor before print. The pass-four sort demo is plain Node — no 'localStorage', no DOM.

Now save a second copy beside the first — 'tasks-annotated.html' or similar. "I understand it now" is a feeling; a second file is an object.

Pass one: one plain sentence per block

In the annotated copy, write a comment above each block saying what it holds up — not what it does, what stops working without it.

Mine:

  • 'loadTasks' — reads saved tasks from storage, or returns an empty list if none exists yet.
  • 'saveTasks' — writes them back so they survive a refresh.
  • 'render' — empties the list on the page and rebuilds it from storage, in priority order.
  • The click handler — takes what you typed, adds it to storage, clears the box, redraws.
  • 'render()' on the last line — draws the list once when the page opens.

Five sentences, whole file. If you can't write one for a block, don't invent it — leave it blank. Pass two is about exactly those.

Pass two: find three lines you cannot account for

Not three lines you find hard — three lines where, if someone asked "what happens if I delete this," you'd have to guess. Be honest; this pass is worthless if you bluff.

Mine, from that file:

  1. 'return raw ? JSON.parse(raw) : []' — I can read the shape of it, but why the parsing?
  2. 'const ordered = [...tasks].sort(...)' — why the dots? Why not just sort the list?
  3. 'list.innerHTML = ""' — why clear something before filling it?

Write yours as questions above the line. Three question marks on a Saturday morning is a good result, not a bad one.

Pass three: break one on purpose, and pick the right one

This is the pass nobody teaches — deliberately breaking working code feels like vandalism. It's the cheapest experiment in software: you have a copy, undo, and nothing is on the internet.

There's a rule, and our QC editor found it the hard way: break a line that runs the moment the page opens. She broke two click-only lines and the page came back pixel-identical, the failure visible only in the console — break something that waits for a click and you'll conclude the drill, or you, is broken.

So: delete the very last 'render();' — the bare one at the bottom, not the one inside the click handler. Save, reload.

Before:

My To-Do App
1 - call mum
2 - pay rent
3 - buy milk

After:

My To-Do App

Three items gone. No error, no red. Heading and button look fine, data's still safe in storage — nobody told it to draw. That one line held up everything you could see.

Put it back. Now do the quieter one: change 'const ordered = [...tasks].sort((a, b) => a.priority - b.priority);' to 'const ordered = tasks;'. Save, reload.

My To-Do App
3 - buy milk
1 - call mum
2 - pay rent

Nothing is missing. Nothing is red. The app is simply, silently, answering a different question — showing you what you added first instead of what matters most.

That's the 66% in one screenshot: almost right, but not quite.

And the third line, 'list.innerHTML = ""'? Delete it, reload, and the page looks perfect — the list was already empty on load. It only breaks on click: rows duplicate instead of clearing first. That's a finding, not a failed experiment: nothing on load, everything on the second render.

Pass four: ask why this and not something simpler

Go back to the assistant that wrote the file, paste the line, and ask it plainly: why this, and not something simpler?

The parsing question has a short answer: storage holds text only, so 'JSON.stringify' flattens your list into a string going in, 'JSON.parse' rebuilds it coming out. Skip the stringify and storage gets the literal text "[object Object]" — every load after throws on the parse, so the list stalls at the item that broke it.

The comparator was the one I actually wanted: why '.sort((a, b) => a.priority - b.priority)' and not just '.sort()'? These are numbers. Sorting is what 'sort' does.

Here's the answer, run rather than taken on trust:

[10, 3, 2].sort()             gives  10, 2, 3
[10, 3, 2].sort((a,b)=>a-b)   gives  2, 3, 10

With no comparator, JavaScript converts every item to text before comparing, and "10" comes before "2" the way "apple" comes before "banana". The plain version is correct 1 through 9, quietly wrong at 10 — right on everything you'd think to test.

And the spread, '[...tasks]'? 'sort' mutates its target, so the dots copy first — true, but the original here comes fresh from storage every render and is thrown away a moment later, so the copy changes nothing I can see.

So my annotation for that line reads: "Makes a copy so the sort doesn't rearrange the original. I don't know what this holds up yet."

Leave that sentence in. A file where every note sounds confident is a file written by someone who finished. Most of us haven't.

Why this matters

The reason to read the file isn't that you'll be quizzed. It's that "it works" and "it works for the reason I think it does" are different states that look identical from outside. Each of those breaks took about fifteen seconds — vandalism that buys a line you never have to guess about again.

There's a date this stops being optional: once the project's online, anything the browser downloads, anyone can read. So spend ten seconds before you deploy — search the annotated file for 'key', 'secret', 'API', and 'password'. If a real one turns up, pull it and treat it as burned: go back to wherever it was issued, delete it there, and generate a new one. Removing it from the file doesn't un-publish it. Better to find that today, with nobody watching.

Final thought

You should now have two files: the original, untouched and working, and a copy carrying a sentence above every block, three questions you couldn't answer, and a note of what stopped when you broke a line on purpose.

One thing before you close the laptop: point at three lines in that second file and say out loud what each is holding up. Not to anyone — just out loud. If one comes out "I don't know yet," write that down too — the most useful sentence in the file, the only one that tells you where to look next Saturday.

The Bug That Doesn't Crash Anything | Vibecodes