hermes - ✅(Solved) Fix [Bug]: `hermes sessions delete` leaves `session_<id>.json` orphaned on disk (writer/remover filename mismatch) [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#20334Fetched 2026-05-06 06:37:19
View on GitHub
Comments
0
Participants
1
Timeline
5
Reactions
0
Author
Participants
Timeline (top)
labeled ×4cross-referenced ×1

Error Message

for suffix in (".json", ".jsonl"): p = sessions_dir / f"{session_id}{suffix}" try: p.unlink(missing_ok=True) except OSError: pass

Fix Action

Fix / Workaround

  • Writer (uses session_ prefix):
    • run_agent.py:1666self.session_log_file = self.logs_dir / f"session_{self.session_id}.json"
    • cli.py:5305self.agent.logs_dir / f"session_{new_session_id}.json"
  • Remover (no prefix):
    • hermes_state.py::_remove_session_files, ~lines 2014–2038:
      for suffix in (".json", ".jsonl"):
          p = sessions_dir / f"{session_id}{suffix}"
          try:
              p.unlink(missing_ok=True)
          except OSError:
              pass
  • Callers (both pass sessions_dir correctly, so the path itself does get exercised):
    • delete_session()hermes_state.py:2070
    • prune_sessions()hermes_state.py:2126
    • prune_empty_ghost_sessions()hermes_state.py:718
    • CLI dispatch: hermes_cli/main.py:9896 (delete), :9911 (prune)

PR fix notes

PR #20342: fix(state): use session_ prefix in _remove_session_files for .json cleanup

Description (problem / solution / changelog)

Summary

Fixes #20334

hermes sessions delete and hermes sessions prune remove the SQLite row, the .jsonl transcript, and request_dump_* files — but silently leave the per-session session_<id>.json dump orphaned on disk.

Root Cause

Filename prefix mismatch between writer and remover:

  • Writer (run_agent.py:1666, cli.py:5305): creates session_{session_id}.json
  • Remover (hermes_state.py::_remove_session_files): looked for {session_id}.json (no session_ prefix)

Path.unlink(missing_ok=True) swallowed the "file not found" error, so the orphan accumulated silently.

On a real system with 2400+ sessions, this means ~2400 orphaned session_*.json files never get cleaned up.

Fix

Replace the suffix loop with explicit candidate paths matching the actual writer conventions:

# Before (wrong prefix for .json)
for suffix in (".json", ".jsonl"):
    p = sessions_dir / f"{session_id}{suffix}"

# After (matches writer conventions)
candidates = [
    sessions_dir / f"session_{session_id}.json",  # run_agent.py / cli.py
    sessions_dir / f"{session_id}.jsonl",          # transcript log
]
for p in candidates:

Updated the existing test_auto_prune_deletes_transcript_files to use the correct session_ prefix for .json files.

Regression Coverage

  • test_delete_session_removes_session_prefixed_json — verifies delete_session() cleans up session_<id>.json
  • test_prune_removes_session_prefixed_json — verifies prune_sessions() cleans up session_<id>.json
  • Updated test_auto_prune_deletes_transcript_files — uses correct session_old1.json filename

Testing

tests/test_hermes_state.py: 212 passed in 1.31s

RED-GREEN cycle confirmed: new tests fail without the fix (assertion error on session_<id>.json still existing), pass with it.

Fixes [bug] hermes sessions delete leaves session_<id>.json orphaned on disk (writer/remover filename mismatch) #20334

Changed files

  • hermes_state.py (modified, +10/-4)
  • tests/test_hermes_state.py (modified, +36/-2)

Code Example

for suffix in (".json", ".jsonl"):
        p = sessions_dir / f"{session_id}{suffix}"
        try:
            p.unlink(missing_ok=True)
        except OSError:
            pass

---

$ ls ~/.hermes/sessions/ | head
20260505_102819_5b83f0b8.jsonl              ← transcript log (no prefix)
session_20260505_102819_5b83f0b8.json       ← per-session JSON (with "session_" prefix)

---

for suffix in (".json", ".jsonl"):
    if suffix == ".json":
        p = sessions_dir / f"session_{session_id}.json"
    else:
        p = sessions_dir / f"{session_id}{suffix}"
    p.unlink(missing_ok=True)
RAW_BUFFERClick to expand / collapse

TL;DR

hermes sessions delete <id> (and hermes sessions prune) remove the SQLite row, the <id>.jsonl transcript, and any request_dump_<id>_*.json files — but silently leave the per-session session_<id>.json transcript dump orphaned on disk. The cause is a literal filename-prefix mismatch between the writer (session_<id>.json) and the cleanup helper (<id>.json). Path.unlink(missing_ok=True) swallows the resulting "file not found", so no error surfaces.

Where

  • Writer (uses session_ prefix):
    • run_agent.py:1666self.session_log_file = self.logs_dir / f"session_{self.session_id}.json"
    • cli.py:5305self.agent.logs_dir / f"session_{new_session_id}.json"
  • Remover (no prefix):
    • hermes_state.py::_remove_session_files, ~lines 2014–2038:
      for suffix in (".json", ".jsonl"):
          p = sessions_dir / f"{session_id}{suffix}"
          try:
              p.unlink(missing_ok=True)
          except OSError:
              pass
  • Callers (both pass sessions_dir correctly, so the path itself does get exercised):
    • delete_session()hermes_state.py:2070
    • prune_sessions()hermes_state.py:2126
    • prune_empty_ghost_sessions()hermes_state.py:718
    • CLI dispatch: hermes_cli/main.py:9896 (delete), :9911 (prune)

What gets cleaned vs. left behind

hermes sessions delete <id> removes:

ArtifactRemoved?
SQLite sessions row + child messages rows
<sessions_dir>/<id>.jsonl (transcript log)✓ (matches deleter pattern)
<sessions_dir>/request_dump_<id>_*.json
<sessions_dir>/session_<id>.json (per-session JSON dump)orphaned

Reproduction (conceptual — no live delete required)

The bug is observable from the on-disk filename pattern alone, without needing to delete anything:

$ ls ~/.hermes/sessions/ | head
20260505_102819_5b83f0b8.jsonl              ← transcript log (no prefix)
session_20260505_102819_5b83f0b8.json       ← per-session JSON (with "session_" prefix)

Every persisted session writes session_<id>.json. The deleter constructs <id>.json (no prefix) and silently misses every time, since no such file is ever created.

Suggested fix

Either is fine; pick whichever matches the project's intent:

Option A — make the deleter match the writer:

for suffix in (".json", ".jsonl"):
    if suffix == ".json":
        p = sessions_dir / f"session_{session_id}.json"
    else:
        p = sessions_dir / f"{session_id}{suffix}"
    p.unlink(missing_ok=True)

Option B — drop the session_ prefix in the writer (more invasive; risks breaking any external tooling that reads session_*.json by that pattern).

Option A is the minimal safe fix.

Impact

Low severity but cumulative. Each leaked file is ~80–150 KB (per-session transcript dump, including system prompt and tool definitions). On long-running installs that periodically prune sessions, the disk cost grows without bound, and the orphans become unattributable once the matching SQLite row is gone (no more title/preview to identify them).

Environment

  • Hermes Agent v0.12.0 (2026.4.30)
  • HEAD at the time of writing: 87b113c2e3d89643f877c8273517c2a48a22253d (current origin/main)
  • Platform Linux, Python 3.11.15
  • Discovery context Operator investigation while diagnosing ~/.hermes/sessions/ directory hygiene; no live delete was performed against real sessions — the bug is fully apparent from source-reading combined with on-disk filename patterns. Happy to run a live confirmation against a sacrificial session if useful, but the source citations make the mismatch unambiguous.

extent analysis

TL;DR

The issue can be fixed by updating the hermes_state.py file to correctly remove the per-session JSON transcript dump by matching the filename prefix used by the writer.

Guidance

  • The root cause of the issue is a filename prefix mismatch between the writer and the remover, causing the remover to silently fail to delete the per-session JSON transcript dump.
  • To fix this, update the hermes_state.py file to use the correct filename prefix when removing the per-session JSON transcript dump, as shown in the suggested fix Option A.
  • Verify the fix by checking that the per-session JSON transcript dump is correctly removed after running hermes sessions delete <id>.
  • Consider the potential impact of the fix on external tooling that may rely on the existing filename pattern.

Example

for suffix in (".json", ".jsonl"):
    if suffix == ".json":
        p = sessions_dir / f"session_{session_id}.json"
    else:
        p = sessions_dir / f"{session_id}{suffix}"
    p.unlink(missing_ok=True)

Notes

The suggested fix Option A is the minimal safe fix, as it only updates the remover to match the existing writer behavior. Option B is more invasive and may break external tooling that relies on the existing filename pattern.

Recommendation

Apply workaround Option A, as it is the minimal safe fix that corrects the filename prefix mismatch without introducing potential breaking changes to external tooling.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING