What healing does
On every conversation read, three repair passes run, in this order: _cleanup_empty_assistant_turns first, because an empty assistant shell would otherwise block the heal; then _heal_incomplete_turns; then _cleanup_dangling_parent_ids. Between the last two, any assistant row missing its tool-use record is backfilled from the JSONL.
Reconstruction
Walk the JSONL, find any user message with no completed assistant child, accumulate the streaming deltas (keyed by pippa.user_msg_id), write a synthesized assistant row to SQLite. Timestamp it at user.created_at + 1µs, not at heal-time — so ORDER BY created_at produces the natural chain.
Normalization
Cleanup any parent_id that doesn't reference an existing message, re-parent it to the previous message in created_at order. This kills the ghost-branch mechanism at its root — removing the dangling references entirely so no future placeholder can match against them.
Why on every read
Healing runs idempotently on every GET — except while a stream is live on that conversation, because healing a turn that is still being written would race its writer. There's no separate 'repair' button anymore. The cost is low (it's reading the JSONL anyway), and it means corrupted state from any past version of any client can never reach the screen.