fix(desktop): prevent stale session page from undoing pin/unpin clicks - #74638
fix(desktop): prevent stale session page from undoing pin/unpin clicks#74638TheRealMisterFix wants to merge 1 commit into
Conversation
pullRemotePins() ran before the push pass, so a stale $sessions page carrying the old server value would immediately re-pin a just-unpinned session (or unpin a just-pinned one) before writePin() ever fired. The existing unconfirmed guard only protected against stale pages arriving during an in-flight write, not the gap between the user's click and the write being initiated. Fix: compute a justChanged set (symmetric difference between the local pinned set and what's been successfully mirrored) and pass it to pullRemotePins, which now skips those ids. Re-read the pinned set after pull returns so the push pass sees any adopted/dropped pins.
|
Thanks for isolating the pre-PATCH stale-page race. The premise holds on current main: Problems
Suggested changes
Automated hermes-sweeper review. |
GottZ
left a comment
There was a problem hiding this comment.
This was generated by AI during triage.
Summary
Three PRs address the same pull-before-push race in Desktop session pin synchronization: #74638 skips locally changed IDs during the stale pull, #75295 suppresses the pull when reconciliation originates from a local pin-set change, and #75342 tracks current-process intent separately from restored pins while preserving server authority at boot.
Related pull requests
- #74638
related— (+69/-2) — close as duplicate of #75295: itsjustChangedsymmetric-difference guard fixes the immediate pin/unpin reversal, but it does not address overlapping writes whose older completion can clear the latestunconfirmedguard. This differs from the automated keep-open review on #74638 because the complete diff still lacks the requested pin→unpin and unpin→pin deferred-promise coverage, while #75295 provides the narrower source-based reconciliation split. - #75295
duplicate— (+44/-5) — keep open with a salvage path: the source-tagged reconciliation directly separates local push-only events from boot/session-list pulls and tests both stalepinned:falseand stalepinned:truerows. Consistent with its automated keep-open review, retain this focused mechanism, then add overlapping-write regression tests and preserve only the latest per-ID write guard. - #75342
duplicate— (+154/-33) — close as duplicate of #75295 after salvaging its restart-after-remote-unpin test: the revised diff now distinguishes current-processlocalIntentfrom restored localStorage state and therefore addresses the contributor's cross-app authority objection. Despite the keep-open review on #75342, the updated mechanism remains a substantially larger competing implementation of the same race; its unique boot regression can be ported without retaining the duplicate synchronization design.
Duplicates
#74638, #75295, and #75342 repair the same stale pull-before-push pin-sync race with different guards. #75295 is also explicitly identified in discussion as a duplicate of #74788; the consolidation chain should therefore be checked against #74788 before final closure.
Suggested consolidation
Keep #75295 open with a salvage path: use its focused source-based pull gating as the consolidation point, add deferred pin→unpin and unpin→pin tests with a generation-safe per-ID unconfirmed guard, and port #75342's restart-after-remote-unpin regression. Then close #74638 as a duplicate of #75295 because its narrower justChanged guard leaves the documented overlapping-write gap, and close #75342 as a duplicate of #75295 once its boot-authority test is preserved; also resolve the existing #75295 → #74788 duplicate relationship before choosing the surviving PR.
Complex graph
flowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
subgraph Dup74638 ["PRs duplicating each other"]
P74638["PR #74638 (open)"]
P75295["PR #75295 (open)"]
P75342["PR #75342 (open)"]
end
class P74638 open
class P75295 open
class P75342 open
class P74638 target
click P74638 "https://github.com/NousResearch/hermes-agent/pull/74638"
click P75295 "https://github.com/NousResearch/hermes-agent/pull/75295"
click P75342 "https://github.com/NousResearch/hermes-agent/pull/75342"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).
Cross-PR triage: Reviewed 3 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 19 kB of PR diffs, 7 kB of issue/PR text, 3 kB of discussion (6 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.
|
Superseded by #80711. Same stale-page-undoes-the-click race; the pin guard now holds until the written value comes back. |
|
Superseded by #80711. Same stale-page-undoes-the-click race; the pin guard now holds until the written value comes back. |
Problem
Clicking pin or unpin on a session in the desktop app had no effect — the pin state would snap back to its previous value immediately.
Root Cause
session-pin-sync.tsreconciles the sidebar's local pin state (localStorage) with the backendsessions.pinnedflag in state.db. Thereconcile()function runspullRemotePins()before the push pass writes the user's change to the backend.When the user clicks unpin:
reconcile()pullRemotePins()runs first — but the `` page still carries the old server value (pinned=true)pullRemotePinssees: server sayspinned=true, local set doesn't have it, nounconfirmedwrite in flight yet → re-pins it immediately and adds it tomirroredmirrored→ skips the unpin writeThe same race happens in reverse when pinning a session the server says is
pinned=false.The existing
unconfirmedguard only protected against stale pages arriving during an in-flight write — not the gap between the user's click andwritePin()being called.Fix
Compute a
justChangedset — the symmetric difference between the local pinned set and what's been successfully mirrored — and pass it topullRemotePins(), which now skips those ids entirely. This prevents the stale server page from undoing the user's click before the push pass writes the correct value.The pinned set is re-read after
pullRemotePins()returns (since it may have adopted/dropped pins from the server) so the push pass sees the updated state.Test Plan
session-pin-sync.test.tspasspinned=falsedoes not undo a just-made pinpinned=truedoes not re-pin a just-unpinned session