mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-27 17:58:07 +00:00
fix(sessions): point failed in-place repair at offline recovery
A failed `hermes sessions repair` printed "keep state.db and the backup" and stopped, so a user whose sessions had vanished had no way to discover that a non-destructive recovery path exists — the reported dead end. The failure branch now names the next command, read-only step first, seeded with the backup path it just preserved. Covered by a CLI-surface regression test that drives the real subprocess against an unrepairable database.
This commit is contained in:
parent
a9b8128bcb
commit
ec2a0f8c1e
2 changed files with 49 additions and 0 deletions
|
|
@ -16133,6 +16133,19 @@ def main():
|
|||
if report.get("backup_path"):
|
||||
print(f" A backup is preserved at: {report['backup_path']}")
|
||||
print(" Keep state.db and the backup; do not delete them.")
|
||||
# Without this pointer the user is at a dead end: in-place
|
||||
# repair has failed and nothing tells them the non-destructive
|
||||
# offline recovery path exists. Lead with --inspect-only so
|
||||
# they confirm the data is readable before writing anything.
|
||||
print("")
|
||||
print(" Next step — offline recovery (never modifies the source):")
|
||||
source_hint = report.get("backup_path") or db_path
|
||||
print(f" hermes sessions recover --source {source_hint} \\")
|
||||
print(" --inspect-only")
|
||||
print(" If that reports the data is recoverable, rebuild it into")
|
||||
print(" a NEW database (the active one is left untouched):")
|
||||
print(f" hermes sessions recover --source {source_hint} \\")
|
||||
print(" --output recovered-state.db")
|
||||
return
|
||||
|
||||
if action == "recover":
|
||||
|
|
|
|||
|
|
@ -323,3 +323,39 @@ def test_cli_recover_writes_verified_report_without_touching_source(
|
|||
assert report["complete"] is True
|
||||
assert report["verification"]["table_counts"]["sessions"] == expected["sessions"]
|
||||
assert report["verification"]["table_counts"]["messages"] == expected["messages"]
|
||||
|
||||
|
||||
def test_failed_repair_points_the_user_at_offline_recovery(tmp_path: Path) -> None:
|
||||
"""A failed in-place repair must name the offline recovery command.
|
||||
|
||||
This is the reported dead end: `hermes sessions repair` exhausts its
|
||||
in-place ladder, prints "keep the files", and stops -- leaving the user
|
||||
with no idea that a non-destructive recovery path exists. The failure
|
||||
branch has to hand them the next command, inspection first, seeded with
|
||||
the preserved backup it just made.
|
||||
"""
|
||||
hermes_home = tmp_path / "isolated-hermes-home"
|
||||
hermes_home.mkdir()
|
||||
# Not a database at all -> every in-place repair strategy fails.
|
||||
(hermes_home / "state.db").write_bytes(b"SQLite format 3\x00" + b"\x7f" * 2048)
|
||||
env = os.environ.copy()
|
||||
env["HERMES_HOME"] = str(hermes_home)
|
||||
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "hermes_cli.main", "sessions", "repair"],
|
||||
cwd=Path(__file__).resolve().parents[2],
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60,
|
||||
check=False,
|
||||
)
|
||||
|
||||
combined = result.stdout + result.stderr
|
||||
assert "Repair failed" in combined, combined
|
||||
# The pointer, and specifically the read-only step first.
|
||||
assert "sessions recover" in combined, combined
|
||||
assert "--inspect-only" in combined, combined
|
||||
# It must offer the source it actually preserved, not a placeholder.
|
||||
assert "--source" in combined, combined
|
||||
assert "malformed-backup" in combined, combined
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue