fix(gateway): resolve session_key in shutdown-flush recovery - #75536
fix(gateway): resolve session_key in shutdown-flush recovery#75536spfcraze wants to merge 1 commit into
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for tracing this through the real adapter-pending representation. The premise is confirmed on current main: gateway/shutdown_flush.py:228-242 only accepts data.session_id, while gateway/platforms/base.py:2051-2063 defines MessageEvent without that field. The proposed fallback at gateway/shutdown_flush.py:239-243 and startup wiring at gateway/run.py:25449-25451 fit the existing lock-held SessionStore.peek_session_id() accessor (gateway/session.py:2905-2919).
Problems
tests/gateway/test_shutdown_flush.py:154-158mocks the resolver and DB, so it does not exercise persisted routing reload through the realSessionStorestartup boundary.
Suggested changes
- Add a temp-HERMES_HOME integration test that persists a session-key mapping, constructs a fresh
SessionStore, and recovers a text-only payload viapeek_session_id.
Automated hermes-sweeper review.
| mock_db = MagicMock() | ||
| count = recover_pending_to_db( | ||
| mock_db, | ||
| session_resolver=lambda key: "20260731_abc123" if key == "agent:main:telegram:dm:42" else None, |
There was a problem hiding this comment.
This proves the callback fallback, but not the startup integration. Please add a temp-HERMES_HOME test using a fresh real SessionStore whose persisted routing entry is reloaded through peek_session_id, then verify recovery appends to that session ID.
recover_pending_to_db skipped every real flush file: _serialise_value captures only the text field from adapter MessageEvent objects (they have no session_id attribute), so recovery always hit the 'no session_id' skip branch — messages queued during a gateway drain were written to disk and then silently never re-ingested, despite the user-facing 'queued for the next turn' promise. The existing tests masked it by hand-writing session_id into payloads real events never produce. Add an optional session_resolver parameter and wire it to SessionStore.peek_session_id at the startup call site; files remain preserved (with the warning) when a key genuinely can't resolve.
fca1c1d to
b1db791
Compare
|
Addressed in the latest push ( Integration test added — Sabotage check: reverting the fix fails both the resolver unit test and the integration test (6 pass / 2 fail); restored, 8/8 in the file. ruff + diff-check clean. |
What does this PR do?
Fixes the #72680 pending-message recovery never recovering anything: every real shutdown-flush file is silently skipped at startup.
The mechanism was built so a message arriving while the gateway is draining ("⏳ queued for the next turn after it comes back") is written to
<hermes_home>/pending_messages/and re-ingested on the next boot. But the data path is broken end-to-end:_serialise_valuecaptures onlytextfrom adapter_pending_messagesvalues — they areMessageEventobjects, which have nosession_idattribute (that lives onevent.source, which isn't serialised).recover_pending_to_dbtherefore always lands in the "no session_id in flush file" branch: warning logged, message skipped, file preserved — on every boot, forever.The existing tests masked it by hand-writing
session_idinto flush payloads that realMessageEvents never produce.The fix adds an optional
session_resolverparameter torecover_pending_to_dband wires it at the startup call site toSessionStore.peek_session_id(the existing lock-held session_key → session_id accessor). Files with genuinely unresolvable keys keep the current preserve-and-warn behavior.Related Issue
No GitHub issue — discovered via code review and reproduced live (see below). Happy to file one first if preferred.
Changes Made
gateway/shutdown_flush.py:recover_pending_to_dbgainssession_resolver; whensession_idis absent from the payload it triessession_resolver(session_key)before falling back to the preserve-and-warn skip. Docstring documents why real flush files lacksession_id.gateway/run.py: the startup recovery call passessession_resolver=runner.session_store.peek_session_id.tests/gateway/test_shutdown_flush.py: three regression tests — a text-only (real-MessageEvent-shaped) payload is recovered via the resolver and the file deleted; the same payload without a resolver is skipped with the file preserved; and an integration test through the real startup boundary — a mapping persisted by oneSessionStore, reloaded by a fresh one, recovered viapeek_session_id(the exactrun.pywiring, no mocks).How to Test
Reproduction (against pre-fix code, real
MessageEventthrough the real serialiser):Focused validation completed:
bash scripts/run_tests.sh tests/gateway/test_shutdown_flush.py— 8/8 pass.test_gateway_shutdown.py test_13121_shutdown_inflight_transcript_flush.py test_pending_drain_no_recursion.py test_bounded_adapter_teardown.py+ the flush file — 22/22 pass.bash scripts/run_tests.sh(branch): 22,920 pass / 98 fail. Baseline on cleanmain(this branch's parent), same machine: 22,917 pass / 99 fail — zero branch-only failures (environment-dependent lanes only). Nothing in the shutdown/flush surface fails.uvx --from ruff==0.15.10 ruff check gateway/shutdown_flush.py gateway/run.py tests/gateway/test_shutdown_flush.py— clean.git diff --check— clean.None(unknown key) falls back to preserve-and-warn; payloads that DO carrysession_idstill take the direct path; resolver raising is contained (falls back, no crash).The full repo-wide suite was run locally (item 4) with zero branch-only failures vs clean
main; GitHub CI remains the final confirmation environment.Logs
Sabotage verification output: