fix(web): skip history replacements whose target seq is absent - #9
fix(web): skip history replacements whose target seq is absent#9Antisophy wants to merge 1 commit into
Conversation
A replace-style boundary event can reference a message the client does not have: a live boundary broadcast racing a still-streaming history load, or a boundary persisted under an older seq numbering (observed after the 2026-07-20 upstream rebase on a large compacted session: 'History replacement matched 0 messages at seq 7433'). The reducer threw for that case, which wedged the whole session view on every load, permanently sticking the task at 'Loading session...'. Drop the unmatched replacement with a console diagnostic instead: the cost is one message rendering without its replacement, against a permanently unviewable session. Structural violations (multiple matches, identity mismatch) still throw. The history-replay flush path already treats a missing target as 'defer'; this aligns the reducer's own behavior for the paths without a defer queue.
|
Sorry, I can't make any sense of this.
This situation is impossible. We enqueue a task's entire history atomically; no backend-side event can race it, by design.
|
|
@Antisophy It looks like Claude is having trouble diagnosing the trickier bugs. It might be more worthwhile to file these as issues, so that I can investigate them properly. |
|
For clear context: I had a task that was guaranteed to not load, making Cydo no longer usable. This fix allowed the task to load, making Cydo usable again. Claude did not have any trouble diagnosing the issue, and this solution successfully resolved it. |
I'm sorry, but it's possible to create a patch that incidentally resolves a problem while still being completely wrong about your understanding of the problem or why your patch fixes it. I believe this is one of those situations, along with some of the other PRs here. We need to be very careful about merging these sorts of patches, because they permanently codify falsehoods, and the accumulated web of lies will ultimately make the entire project unmaintainable and destroy it. |
replaceHistoryBoundarythrows when a replace-style boundary event references a message seq the client's reduced state does not contain. Two ways to get there in practice:pendingHistoryBoundariesdefer only guards the separately-routedtask_history_boundary_replacedkind; boundary-carrying events flowing throughhandleTaskMessagereach the reducer unguarded), andHistory replacement matched 0 messages at seq 7433, which permanently wedged the session view at "Loading session..." with no recovery short of code changes.Since the throw converts a cosmetic inconsistency into an unviewable session, this makes the matched-0 case fail soft: drop the replacement with a console diagnostic and return the state unchanged. The cost is one message rendering without its replacement until seqs realign. The structural invariants keep throwing (multiple matches, canonical-identity mismatch), and the replay flush path's defer behavior is unchanged; this only aligns the reducer's own behavior for the paths that have no defer queue.
The existing "rejects invalid replacement targets" test pinned the throw; it now asserts the skip returns the same state instance. Full
nix flake checksuite is green on the branch.