fix(desktop): always send a durable truncation address on rewind/edit/regenerate - #87294
Merged
Conversation
A user turn whose submit failed keeps its optimistic bubble but never reached the gateway, so counting it makes every later truncate_before_user_ordinal overshoot the backend index (refused 4018, regenerate dead for the rest of the session). Skip failed turns in the one shared visible-user ordinal space (visibleUserMessageIndices) used by truncate ordinals, ordinal->index resolution, and survivor-rowId rebinding. Based on #41275 by @vondelomlo, relocated onto the split use-prompt-actions/ modules and widened from visibleUserOrdinal to the shared index helper.
…w ids by content Client half of #87059. The gateway now fails ordinal-only truncation closed for durable sessions (#87150), which turned the mis-aimed cut into a visible edit-resend error for any bubble without a bound rowId (edit after an interrupted turn, unstamped resume). Make the Desktop always produce a durable address or degrade safely: - runRewindSubmit: when a truncation request lacks a durable address, resolve the target's row id by exact content against session.history (which ships row_id per persisted row). Resolution is exact-or-nothing: a unique text match wins; ambiguity is accepted only when the target is provably the newest persisted turn (the edit-after-interrupt shape). Anything else degrades to a PLAIN resubmit — never a guessed cut. The client ordinal is dropped either way (its space can diverge from the gateway's — the #87059 root). - planReload/planRestore: degrade failed turns to a plain resubmit (extends the #86623 pattern to regenerate/restore) and carry the turn's persisted sourceText as the content key. - rebindSurvivorRowIds: iterate the same failed-turn-aware ordinal space as the truncate math. - session-tile-actions: reload goes through the shared runRewindSubmit primitive instead of a raw prompt.submit, so the tile surface gets the same discipline.
Contributor
૮ >ﻌ< ა ci reviewran on fd3e43d — fix(gateway): session.history ships durable row_id stamps
|
The Desktop's content-based truncation-target resolution (and reactions) address persisted turns by row_id, but session.history loaded the transcript without include_row_ids=True, so _history_to_messages had no stamp to forward and the projection silently stripped the one durable address clients can use. Discovered live-testing the #87294 client flow: resolveDurableRowId saw 0 stamped rows and degraded every edit to a plain resubmit.
This was referenced Aug 15, 2026
atirna
pushed a commit
to atirna/hermes-agent
that referenced
this pull request
Aug 17, 2026
The Desktop's content-based truncation-target resolution (and reactions) address persisted turns by row_id, but session.history loaded the transcript without include_row_ids=True, so _history_to_messages had no stamp to forward and the projection silently stripped the one durable address clients can use. Discovered live-testing the NousResearch#87294 client flow: resolveDurableRowId saw 0 stamped rows and degraded every edit to a plain resubmit.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Desktop rewind/edit/regenerate now always addresses the turn it means: every truncating
prompt.submitcarries a durabletruncate_before_row_id, resolved by content from the gateway's stamped transcript when the bubble never learned its id — and when no durable address can be proven, the client degrades to a plain resubmit instead of a guessed cut. Client half of #87059 (the gateway half, #87150, fails ordinal-only truncation closed; this PR removes the user-facing error that fail-closed left behind).Root cause recap: the renderer counts visible user bubbles while the gateway counts persisted user rows (synthetic injections, failed turns) — the two ordinal spaces diverge, and an edit of an interrupted turn had no bound rowId, so the Desktop fell back to ordinal-only addressing (78-message mis-cut in #87059; refused with 4004 since #87150).
Changes
use-prompt-actions/rewind.ts:resolveDurableRowId()— resolves a turn's durable row id by exact content match againstsession.history(which shipsrow_idper persisted row). Exact-or-nothing: unique match wins; ambiguity accepted only when the target is provably the newest persisted turn (the edit-after-interrupt shape); anything else → no truncation.runRewindSubmit()— when a truncation request lacks a durable address, resolve first; on failure degrade to a plain resubmit. The divergent client ordinal is dropped either way.planReload/planRestore— degrade failed turns to plain resubmit (extends fix(desktop): stop retry/restore from truncating unpersisted failed turns #86623'splanEditpattern); all plans carrysourceTextas the content key.rebindSurvivorRowIds— iterates the same failed-turn-aware ordinal space as the truncate math.use-prompt-actions/utils.ts:visibleUserMessageIndices()— the ONE backend-facing ordinal space (visible AND persisted), shared by ordinal math, ordinal→index resolution, and survivor rebinding. Failed turns are skipped (salvages fix(desktop): skip failed turns in regenerate/edit truncation ordinal #41275 by @vondelomlo, relocated onto the split modules).session-tile-actions.ts: tile regenerate goes through the sharedrunRewindSubmitprimitive instead of a rawprompt.submit, so both surfaces keep the same discipline.Validation
Attribution
Commit 1 salvages #41275 (@vondelomlo) — failed-turn ordinal skip — preserved as author.
Infographic
Infographic generation temporarily unavailable (image backend balance exhausted at PR time); will be added by
gh pr editwhen the backend recovers.