Skip to content

fix(desktop): always send a durable truncation address on rewind/edit/regenerate - #87294

Merged
teknium1 merged 3 commits into
mainfrom
fix/desktop-durable-rewind-addressing
Aug 15, 2026
Merged

fix(desktop): always send a durable truncation address on rewind/edit/regenerate#87294
teknium1 merged 3 commits into
mainfrom
fix/desktop-durable-rewind-addressing

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Desktop rewind/edit/regenerate now always addresses the turn it means: every truncating prompt.submit carries a durable truncate_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 against session.history (which ships row_id per 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's planEdit pattern); all plans carry sourceText as 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 shared runRewindSubmit primitive instead of a raw prompt.submit, so both surfaces keep the same discipline.

Validation

Scenario Before After
Edit after interrupted turn (no bound rowId) ordinal-only → 4004 error (post-#87150) / 78-msg mis-cut (pre) row id resolved by content → exact 1-turn cut
Unresolvable target (ambiguous/missing content) mis-aimed cut or error plain resubmit, history untouched
Regenerate after a failed turn every later ordinal overshoots (4018, regenerate dead) failed turns excluded from ordinal space
Tests 825/825 desktop vitest pass; typecheck (3 tsconfigs) + eslint 0 errors

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 edit when the backend recovers.

vondelomlo and others added 2 commits August 15, 2026 14:54
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.
@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on fd3e43d — fix(gateway): session.history ships durable row_id stamps

⚠️ Warnings

OSV vulnerability scan · View job

5 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.


debug info

CI timings

CI timings · View report · View job

Wall time 3m40s vs 3m37s (+1.4%). 19 job(s) slower, 15 faster, 3 unchanged.

  • JS & TS checks / apps/desktop / check:test:ui:shard-3of3: +36.0s
  • Python tests / Run tests slice 6/12: +19.0s
  • JS & TS checks / apps/desktop / check:test:ui:shard-1of3: +18.0s
  • Python tests / Run tests slice 5/12: +17.0s
  • Python tests / Run tests slice 4/12: +14.0s

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.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/desktop Electron desktop app (apps/desktop/*) comp/tui Terminal UI (ui-tui/ + tui_gateway/) area/sessions Session lifecycle, resume, persistence, history sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 15, 2026
@teknium1
teknium1 merged commit 2e9dcb7 into main Aug 15, 2026
60 checks passed
@teknium1
teknium1 deleted the fix/desktop-durable-rewind-addressing branch August 15, 2026 22:09
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/desktop Electron desktop app (apps/desktop/*) comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants