Skip to content

fix(state): rebuild legacy gateway_routing PK; guard session_store in dispatch hook - #73541

Merged
teknium1 merged 1 commit into
mainfrom
fix/gateway-routing-pk-heal
Jul 28, 2026
Merged

fix(state): rebuild legacy gateway_routing PK; guard session_store in dispatch hook#73541
teknium1 merged 1 commit into
mainfrom
fix/gateway-routing-pk-heal

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Heals live databases stuck with the pre-scope gateway_routing PRIMARY KEY, ending per-save "routing save failed" warning spam, and guards the pre_gateway_dispatch hook's session_store access so the hook always fires.

Root cause: early builds of the #59203 routing-index migration created gateway_routing with session_key TEXT PRIMARY KEY and no scope column. The declarative column reconciler ADDs the missing scope column, but SQLite cannot ALTER a primary key — so the shipped composite PRIMARY KEY (scope, session_key) never lands on those databases and every routing save fails:

  • save_gateway_routing_entryON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint
  • replace_gateway_routing_entriesUNIQUE constraint failed: gateway_routing.session_key whenever the same session_key exists under another scope

One live errors.log carried 261 of these warnings; the store silently falls back to sessions.json every time.

Changes

  • hermes_state.py: new _heal_gateway_routing_pk() — detects the legacy PK via PRAGMA table_info, rebuilds the table once with the composite key, preserves rows (newest wins on collisions, NULL scope coalesced to ''). Wired into _init_schema() right after _reconcile_columns(). Same one-time-heal pattern as the [Bug]: Gateway Memory Loss — INSERT omits active column in hermes_state.py #51646 active-column repair.
  • gateway/run.py: pre_gateway_dispatch kwargs now pass getattr(self, "session_store", None) — a partially-initialized runner previously raised inside the dispatch try-block, logging 'GatewayRunner' object has no attribute 'session_store' and dropping the hook for every message.
  • Tests: TestGatewayRoutingPkHeal (4 cases: rebuild, both write paths post-heal, NULL-scope coalesce, idempotence on modern shape) + hook-fires-without-session_store regression. All fail without their fixes (sabotage-verified).

Validation

Before After
Legacy-PK DB, upsert ON CONFLICT error, warning per save succeeds
Legacy-PK DB, cross-scope replace UNIQUE constraint error succeeds, scopes isolated
Real affected state.db (copy) 261 warnings/log heals on open, 15 live rows preserved
Runner without session_store hook never fires, warning per message hook fires with store=None

scripts/run_tests.sh tests/test_hermes_state.py tests/gateway/test_session.py tests/gateway/test_session_store_lock_io.py → 626 passed, 0 failed. tests/gateway/test_pre_gateway_dispatch.py → 6 passed.

Infographic

infographic

… dispatch hook

Two log-spam bugs found in live gateway logs:

1. gateway_routing UNIQUE-constraint spam (261 warnings in one errors.log):
   early builds of the #59203 routing-index migration created
   gateway_routing with 'session_key TEXT PRIMARY KEY' and no scope
   column. _reconcile_columns() ADDs the missing scope column but SQLite
   cannot ALTER a primary key, so the shipped composite
   PRIMARY KEY (scope, session_key) never lands on those databases. Both
   write paths then fail on every save:
   - save_gateway_routing_entry: 'ON CONFLICT clause does not match any
     PRIMARY KEY or UNIQUE constraint'
   - replace_gateway_routing_entries: 'UNIQUE constraint failed:
     gateway_routing.session_key' whenever the same session_key exists
     under another scope (e.g. test-suite scopes leaked into a live DB).
   New _heal_gateway_routing_pk() rebuilds the table once with the
   composite key, preserving rows (newest wins on collisions, NULL scope
   coalesced to ''). Same one-time-heal pattern as the #51646 active-
   column repair. Verified E2E against a copy of a real affected state.db.

2. pre_gateway_dispatch warned ''GatewayRunner' object has no attribute
   'session_store'' and silently dropped the hook for every message on
   partially-initialized runners (bare object.__new__ runners in tests,
   and any future init-order change). Pass
   getattr(self, 'session_store', None) so the hook always fires
   (pitfall #17 pattern).

Both regression tests fail without their fixes (sabotage-verified).
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 49e13fd

ℹ️ Info

Desktop E2E visual evidence · View test artifacts · View job

1 visual diff.

inline evidence upload failed.

Failed to upload diff-665a0833239e-onboarding-overlay-diff.png with gh image (exit code 1): Error uploading /home/runner/work/_temp/e2e-evidence/diff-665a0833239e-onboarding-overlay-diff.png: step 0 (get upload token): uploadToken not found on repo page — do you have write access to NousResearch/hermes-agent? (or, if NousResearch enforces SAML SSO, authorize at https://github.com/orgs/NousResearch/sso)

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 28, 2026
@teknium1
teknium1 merged commit 76b0ea5 into main Jul 28, 2026
43 checks passed
@teknium1
teknium1 deleted the fix/gateway-routing-pk-heal branch July 28, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades 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.

2 participants