You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Fix: a hand saved before a restart and stored since is reported as a lost row at the next resync #968
DEFECT-CONFIRMED on live evidence, low severity: a diagnostic that states a loss that did not happen. Three false warnings in 35 hours on build 7a1494c; no data lost. Found by the same live-log read as the redundant-join ticket.
Failing scenario
report_unsettled (crates/apps/bot/src/client/recover.rs, the function after resume_open_hand) warns "hand X was in progress at the restart and ended before the resync; ... so its row is lost" for any entry left in shared.resumable when a resync snapshot names another hand. An entry leaves the map only when its hand's result is replayed (replayed_result) or priced live by resume_open_hand, and resume_open_hand returns early when the tracker already knows the hand, without removing the entry. A hand whose row was stored is then reported as lost at the next resync:
SurSvan, 13:34:43 on 10-10: hand 1e8862bf, stored at 11:00:23 (net -165), reported 2 h 34 min later after a connection reset.
Svanism, 14:03:59: hand 7abdd813, stored at 11:00:39 (net -50), same.
SvanBotV10, 11:00:04, at the hot swap: hand 08305a02, stored at 10:59:57 by the process that was swapped out (net -128). The row is in hands and in the history raw table; the operator verified this on 2026-10-10.
The warning exists so that a row lost at a hot swap does not stay silent (0315). A false one at every swap hides a true one, and the next release is a swap.
Fix
Drop the entry without a warning when the store holds the hand. report_unsettled asks shared.store.hand(&bot.name, &saved) the way replayed_result does, and warns only on Ok(None): a store that cannot answer says nothing about the row. One check in the one reporter covers all three causes.
Test
Reproduce first on origin/main: resumable holds hand X, the store holds a row for X, and a resync snapshot names another hand; assert no warning and that the entry is dropped. The counterpart with no stored row still warns.
Pinned for the fix (read-only prework, nothing changed):
Site: report_unsettled in crates/apps/bot/src/client/recover.rs removes the resumable entry and then logs. Between the removal and the log, add if matches!(shared.store.hand(&bot.name, &saved), Ok(Some(_))) { return; }. Store::hand returns Result<Option<HandRow>> (crates/libs/store/src/store/hands.rs:364), and replayed_result already treats anything but Ok(None) as "do not claim a loss".
Test: beside the existing report test in crates/apps/bot/src/client/handler/tests/recover.rs (the one asserting "and dropped, so the next resync does not repeat it"). Same setup, with the saved hand's row stored first; assert no "row is lost" line and the entry gone. The existing test with no stored row must still warn.
report_unsettled now asks the store before it names a hand lost, and says nothing when the row is there. A store that cannot answer still warns. It covers all three false warnings: the hand stored by the swapped-out process, and the two stored by the live path whose saved start stayed in resumable.
Test a_saved_hand_the_store_already_holds_is_not_reported_lost, run red on the unmodified code first; the existing "reported lost once" test still passes. Full gate: 897 passed, 0 failed. CI: check, gate and web green.
Log-only change: no paired simulation, no golden change. Not released: the running build is 7a1494c.
Activity
svanlabs commentedon Oct 12, 2026
Pinned for the fix (read-only prework, nothing changed):
report_unsettledincrates/apps/bot/src/client/recover.rsremoves theresumableentry and then logs. Between the removal and the log, addif matches!(shared.store.hand(&bot.name, &saved), Ok(Some(_))) { return; }.Store::handreturnsResult<Option<HandRow>>(crates/libs/store/src/store/hands.rs:364), andreplayed_resultalready treats anything butOk(None)as "do not claim a loss".crates/apps/bot/src/client/handler/tests/recover.rs(the one asserting "and dropped, so the next resync does not repeat it"). Same setup, with the saved hand's row stored first; assert no "row is lost" line and the entry gone. The existing test with no stored row must still warn.Generated-by: claude-code/claude-sonnet-5-5
svanlabs commentedon Oct 12, 2026
Resolved by pull request 972, merged as 0e3d584.
report_unsettlednow asks the store before it names a hand lost, and says nothing when the row is there. A store that cannot answer still warns. It covers all three false warnings: the hand stored by the swapped-out process, and the two stored by the live path whose saved start stayed inresumable.a_saved_hand_the_store_already_holds_is_not_reported_lost, run red on the unmodified code first; the existing "reported lost once" test still passes. Full gate: 897 passed, 0 failed. CI: check, gate and web green.Generated-by: claude-code/claude-sonnet-5-5