Skip to content

fix(desktop): resolve reaction write key for rehydrated/rotated sessions - #80675

Open
fluxkapacitor wants to merge 1 commit into
NousResearch:mainfrom
fluxkapacitor:fix/reactions-4040-resumed-session-key
Open

fix(desktop): resolve reaction write key for rehydrated/rotated sessions#80675
fluxkapacitor wants to merge 1 commit into
NousResearch:mainfrom
fluxkapacitor:fix/reactions-4040-resumed-session-key

Conversation

@fluxkapacitor

Copy link
Copy Markdown

Summary

Fixes #80670 — the message.react RPC returns 4040 ("Could not react" / "message not found in this session") when reacting to messages in resumed or long-lived conversations, even though every message row exists. Fresh conversations react fine; old/resumed ones hard-fail in the desktop UI.

Root cause

Two session-key resolution gaps in the handler (tui_gateway/methods_session.py):

  1. Missing key on rehydrated sessions. Desktop session rows that predate the session_key column in sessions have session_key = NULL (verified on real data: a 403-message session row with session_key=None while all rows live under session_id). On resume, the registry entry inherits that, and SessionDB.set_message_reaction(None, row_id, …) short-circuits to None (hermes_state.py:5833if not session_id …: return None) → 4040.
  2. Stale key after compression rotation. Auto-compaction forks a continuation child; a registry entry bound to the rotated parent writes against the parent key while post-compaction rows live under the tip — same None → 4040.

The handler wrote with session["session_key"] unconditionally, while the turn payload already uses session.get("session_key") or sid (server.py:1656) — the RPC never got that fallback.

Fix

message.react now:

  • Resolves the write key as session_key or routed session_id (matching the turn payload), so keyless resumed sessions address their durable rows.
  • Retries once through the lineage on a miss: db.resolve_resume_session_id(write_key) (the same continuation-tip resolution session.resume uses), then the routed id as a final candidate. Safe because message row ids are globally unique — a retry can only land on the exact row the client addressed, never on a different message.
  • Logs the 4040 shape (logger.warning with session/row/key) so a genuinely stale client row id is diagnosable instead of a silent dead-end.

Behavior preserved

  • newest_role (live-message, no-row-id) path works through the same key resolution.
  • A genuinely unknown row id still returns 4040 — no wrong-message writes, no security relaxation (rows are still scoped to the owning session via WHERE id=? AND session_id=?).
  • No changes to the storage layer (hermes_state.py untouched), the gate, or any other RPC.

Testing

  • New tests/tui_gateway/test_message_react_rpc.py — 4 tests: keyless-session fallback ✓, lineage-tip retry ✓, role-based path with fallback key ✓, still-4040 with diagnostic log ✓
  • scripts/run_tests.sh tests/tui_gateway/ tests/test_message_reactions.py359 tests, 0 failed

Deliberately not mixed with any other carry (e.g. #69593 BlueBubbles, #80659 reactions gate).

message.react failed with 4040 ("message not found in this session") on
resumed desktop conversations: registry entries for sessions predating the
session_key column carry session_key=None, and auto-compaction rotation can
leave a stale parent key while the durable rows live under the continuation
tip. set_message_reaction(None, ...) short-circuits to None, so the RPC
errored even though every row was present.

Fall back to the routed session id (matching the turn payload's `or sid`)
and, on a miss, retry once via the compression-continuation tip — safe
because message row ids are globally unique. Log the 4040 shape for
diagnosability.
@fluxkapacitor
fluxkapacitor force-pushed the fix/reactions-4040-resumed-session-key branch from 87fd091 to bd13ada Compare August 7, 2026 00:35
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/desktop Electron desktop app (apps/desktop/*) sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 7, 2026
@fluxkapacitor

Copy link
Copy Markdown
Author

Friendly ping — this PR fixes #80670 (4040 "Could not react" on resumed/rotated sessions) with a session-key fallback chain (session_key → routed session_id → lineage-tip retry) plus regression tests, and is currently mergeable against main.

Context for triage: it's distinct from the reactions availability fix (7ad9ace) and the live-paint feature (6ec319f) — both of which are merged. This one covers the write-key resolution path in the message.react RPC for sessions predating the session_key column or rotated by compression. Happy to rebase or adjust if needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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.

fix(desktop): "Could not react" / 4040 on resumed conversations (message.react session-key & row-id resolution)

2 participants