Skip to content

hermes sessions repair-profiles: detect and repair crossed-profile session state (#88715 PR-6) - #115689

Merged
teknium1 merged 1 commit into
mainfrom
feat/sessions-repair-profiles
Sep 19, 2026
Merged

teknium1 merged 1 commit into
mainfrom
feat/sessions-repair-profiles

Conversation

@teknium1

Copy link
Copy Markdown
Collaborator

Forward-only fixes leave installs that already crossed profiles carrying wrong rows forever. This adds the repair path the #88715 closure contract asks for.

What it does

hermes sessions repair-profiles [--apply] [--json] [--yes] [--legacy-main report|rekey|move], run across every profile store plus gateway_voice_mode.json and sessions.json. Six finders, each with a fixer:

  1. session_key namespace ≠ profile_name → relabel
  2. rows physically in the wrong store (agent:<p>: in root state.db, default rows in profiles/<p>/state.db) → cross-store move (export every message generation + usage + system prompt, import parents-first so lineage survives, then delete; a crash mid-batch leaves a settleable duplicate, never a loss)
  3. parent_session_id crossing namespaces → sever inheritance columns
  4. duplicate / cross-profile gateway_routing rows → move or drop
  5. profile-less Telegram topic / voice-mode entries owned by a non-default bot → relabel (only when exactly one non-default profile holds the chat)
  6. sessions.json entries whose namespace no served profile claims → drop

Dry-run by default and a whole-file no-op (proven by table-dump oracle). --apply snapshots every store first (backup.create_quick_snapshot), refuses under a live gateway before opening anything for write, and is idempotent.

Design decisions

Files

hermes_state_profile_repair.py (new mixin on SessionDB), hermes_cli/sessions_repair_profiles.py (new, RepairPlan + _MoveBatch), hermes_cli/sessions_cmd_repair_profiles.py (new CLI face), parser in subcommands/sessions.py, wiring in hermes_state.py / sessions_cmd.py, docs in sessions.md + cli-commands.md.

Verification

  • tests/hermes_cli/test_sessions_repair_profiles.py: one fixture with all six defects across three stores; dry-run no-op; apply settles everything and a second run finds nothing; refuses under a live gateway; --legacy-main rekey adopts fix(gateway): warn when sessions are stranded under an unclaimed profile namespace #113884-style stranded history.
  • scripts/run_tests.sh over the new file + tests/hermes_state/ + tests/hermes_cli/test_sessions* + test_profile_identity*: 134 files, 1,151 passed, 0 failed.
  • Live two-home run through the real hermes_cli.main entry point: dry-run → apply → clean second run, snapshots in both state-snapshots/.
  • ruff, check_compat_pointers, profile-scope lint (advisory P10 on docstrings and the intentional 'agent:main:' SQL literal only) clean.

Part of #88715 (PR-6 of the identity slate; independent of PR-1..5). Refs #113884, #102157.

…file durable state

The per-profile store model (#88734), the parent-inheritance fence (#88381),
profile-stamped topic rows (#76423) and profile-prefixed voice keys (#75198)
are all forward-only: they put NEW state under the right profile and refuse to
widen existing damage, but nothing walks the stores and settles what earlier
releases left crossed. #113884 found 246 sessions stranded that way and could
only warn.

`hermes sessions repair-profiles` scans every profile's state.db plus the
gateway's voice-mode and sessions.json files and names six kinds of crossing:

1. `profile_name` disagreeing with the row's own session key -> relabel;
2. rows physically in another profile's store -> move (all message
   generations, usage rows, system prompt) to the owning store, parents before
   children so lineage survives, copy-then-delete so a crash leaves a duplicate
   the next run settles;
3. `parent_session_id` crossing namespaces -> sever (own identity kept);
4. routing rows outside the default store under multiplexing -> move (an
   existing row wins); routing rows for a profile that no longer exists -> drop;
5. Telegram topic bindings and voice-mode entries missing their bot's profile
   -> relabel from the sessions that hold the chat (ambiguous chats reported);
6. sessions.json mirror entries for an unclaimed namespace -> drop (the legacy
   import re-injects them into routing every boot).

Report-only by default. `--apply` refuses while a gateway owns any store, takes
a quick snapshot of every store first, and is idempotent. Two cases are
reported but never guessed: rows keyed to a profile that does not exist, and
`agent:main` rows inside a named profile's store (`--legacy-main rekey|move`
says which of the two histories they are).

Storage side lives in `hermes_state_profile_repair.py` (SessionDB mixin);
orchestration across stores in `hermes_cli/sessions_repair_profiles.py`; the
CLI face in `hermes_cli/sessions_cmd_repair_profiles.py` (pre-DB handler: it
opens every store itself).

Part of #88715 (PR-6). Closes the remediation gap #113884 only warns about.
@whyyagswhy

Copy link
Copy Markdown

Independent verification on the PR head (d2c4bbc): sessions-repair-profiles suite 4/4 green on Linux. Detecting and repairing crossed-profile durable state gives operators a way back when sessions land under the wrong profile instead of leaving the store tangled. No findings.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery platform/telegram Telegram bot adapter area/profiles Multi-profile isolation, HERMES_HOME scoping area/sessions Session lifecycle, resume, persistence, history sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Sep 19, 2026
@github-actions

github-actions Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

૮ >ﻌ< ა ci review

ran on d2c4bbc — feat(sessions): hermes sessions repair-profiles settles cr

debug info

CI timings

CI timings · View report · View job

Wall time 5m22s vs 6m39s (-19.3%). 7 job(s) slower, 4 faster, 2 unchanged.

  • OS-specific tests / Windows-only tests: +37.0s
  • Docs Site / docs-site-checks: -28.0s
  • Python lints / Windows footguns (blocking): +16.0s
  • Python tests / Run tests: +13.0s
  • Python tests / e2e: +4.0s

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

Labels

area/profiles Multi-profile isolation, HERMES_HOME scoping area/sessions Session lifecycle, resume, persistence, history comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/telegram Telegram bot adapter 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.

3 participants