C.W.K.
Lesson 05 of 06 · published

The Ghost Branch Bug — Defense-in-Depth 4 Layers

~16 min · bug, defense-in-depth

Level 0Curious
0 XP0/52 lessons0/16 achievements
0/100 XP to next level100 XP to go0% complete

The most beautiful bug Dad and I worked on

An earlier version of the frontend left __pending_* magic ids in displayed-message state on abort. A follow-up message computed parentId = lastDisplay.id and that last-display was a stale placeholder — so the new chat request was sent with parent_id = '__pending_assistant' literally. The backend trusted the value, persisted it to SQLite, and returned it on every future GET.

It looked correct in steady state. But during streaming, the frontend adds a fresh placeholder with id literally __pending_assistant. Suddenly the stale parent_id resolves — to the new placeholder. The orphan attaches as a child of the new placeholder. The active-path walk continues into the dead branch and renders it as the tail. Dad sees a 'ghost' of the orphaned turn appear during streaming and disappear when the stream completes.

It's a self-resolving dangling reference — perfectly inert until a new streaming turn introduces a node with the same magic id, at which point it springs to life for one stream and goes back to sleep.

Defense-in-depth — four layers

#LayerWhat it does
1Frontend sendMessageRefuses to use a __pending_* id as parentId; falls back to null.
2Frontend _streamResponse finallyUnconditionally scrubs __pending_* placeholders on every exit path.
3Backend chat handlerRejects any incoming parent_id starting with __pending_.
4Backend GET endpointRuns _cleanup_dangling_parent_ids on every read, repairing any unresolvable parent_id.
The lesson: Client-side magic identifiers must never reach persistent storage. If they do — even once, on one bad version — the corruption can sit dormant for months and resurface in a way nobody predicts. Belt-and-suspenders the boundary, and when client-side state shows up in your database, treat it as a crime scene, not an oddity.

Progress

Progress is local-only — sign in to sync across devices.