fix(sessions): don't let the empty-session sweep delete an archived transcript - #96401
Merged
Merged
Conversation
…ranscript
`count_empty_sessions` / `delete_empty_sessions` — the dashboard's
"Delete empty (N)" affordance — defined "empty" as `sessions.message_count
= 0`. That column is a denormalized counter over the LIVE (`active = 1`)
rows only, and two production transcript-rewrite paths reset it on purpose
while keeping every dropped turn on disk as `active = 0`:
* `replace_messages(..., archive_dropped=True)` — the rewind / edit /
regenerate mode added in NousResearch#82756 so a taken-back turn stays recoverable.
* `archive_and_compact` — in-place compaction, which archives the
pre-compaction transcript under the same session id (NousResearch#38763).
A chat rewound to its first turn, or compacted with an empty live set,
therefore reports `message_count = 0` while still holding its entire
history — and those soft-archived rows are the only copy. A gateway reload
is what makes the row eligible: it stamps `ended_at` on every detached
session (`end_reason='ws_orphan_reap'`), satisfying the sweep's
`ended_at IS NOT NULL` gate. The next sweep then hard-deleted the session
row AND `DELETE FROM messages`, destroying the transcript silently.
Every other emptiness test in `hermes_state` already defends the counter
with a real `EXISTS (SELECT 1 FROM messages ...)` probe
(`delete_session_if_empty`, `prune_empty_ghost_sessions`,
`list_never_active_keyed_sessions`, `find_recoverable_session`). This
sweep was the only destructive path that trusted the counter alone. It now
uses the same probe, via one `_EMPTY_SESSION_WHERE` selector shared by the
count and the delete so the button's N and the sweep it triggers can never
disagree again. The counter stays as a cheap prefilter; `EXISTS` is the
authority.
Genuinely message-less rows are still swept — the feature is unchanged for
the case it was built for.
Fixes NousResearch#95868
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011zTDHnWcBvQsJSX1fBLhuv
…esearch#95868 guards Precision pass on the comments added by the previous commit: prompt.submit reaches replace_messages(archive_dropped=True) with an empty prefix on a confirmed ordinal-0 rewind, which is the production shape that lands a populated session on message_count = 0. archive_and_compact normally publishes at least a summary row, so it is pinned as defense in depth rather than claimed as an equally reachable trigger. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zTDHnWcBvQsJSX1fBLhuv
Deduplicate the explanation across the constant comment, count_empty_sessions docstring, and delete_empty_sessions docstring. Keep the incident refs (NousResearch#70516/NousResearch#80763/NousResearch#82756/NousResearch#95868) and the core WHY on the constant; cross-reference from the methods.
kshitijk4poor
force-pushed
the
review-96092
branch
from
August 27, 2026 14:23
71a01b0 to
afd39ed
Compare
kshitijk4poor
enabled auto-merge (rebase)
August 27, 2026 14:24
5 of 7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Salvage of PR #96092 (@JoaoMarcos44) with a comment trim follow-up: the empty-session sweep can no longer hard-delete a session whose transcript survives as soft-archived (
active=0) rows after a rewind or in-place compaction.Root cause:
count_empty_sessions()/delete_empty_sessions()selected onmessage_count = 0alone. That counter tracks live (active=1) rows only, and two production paths deliberately reset it while keeping dropped turns on disk asactive=0—replace_messages(archive_dropped=True)(rewind, #82756) andarchive_and_compact(in-place compaction). After a gateway reload stampsended_at, such a row satisfied the sweep's gates and was hard-deleted together with itsmessagesrows — silently, with no log line (#95868).Changes
hermes_state.py: shared_EMPTY_SESSION_WHEREselector addsNOT EXISTS (SELECT 1 FROM messages WHERE messages.session_id = sessions.id)as the authority;message_count = 0stays as a cheap prefilter. Same shape as all four sibling guards (delete_session_if_empty,prune_empty_ghost_sessions,list_never_active_keyed_sessions,find_recoverable_session).hermes_cli/web_routers/sessions.py: endpoint docstring updated to match.tests/hermes_state/test_empty_sweep_archived_transcript_95868.py: 5 new tests (3 fail without the fix).#:comment and docstrings to avoid triple-duplication of the same explanation.Validation
Closes #96092
Fixes #95868