mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-25 17:18:11 +00:00
fix(checkpoints): never auto-delete orphans on unattended startup sweep
Builds on this PR's diagnosis by @Frowtek: a missing workdir is ambiguous (deleted project vs. an unmounted external volume / network share / VPN not yet up), so it's not safe evidence for a destructive GC sweep — especially one that runs unattended at startup. - cli.py / gateway/run.py: the startup auto-maintenance sweep now always passes delete_orphans=False to maybe_auto_prune_checkpoints(). It still prunes by retention_days, size cap, and legacy archives — none of which require guessing whether a project was deleted or is just temporarily unreachable. - hermes_cli/config.py: drop the now-unused delete_orphans default. - hermes_cli/checkpoints.py: `hermes checkpoints prune` (the explicit, human-invoked path) now previews the orphan project list and asks for confirmation before deleting, unless -f/--force is passed. - Docs updated (EN + zh-Hans) to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
3960315af7
commit
cd3f653b4e
6 changed files with 64 additions and 18 deletions
|
|
@ -109,20 +109,41 @@ def cmd_list(args: argparse.Namespace) -> int:
|
|||
|
||||
|
||||
def cmd_prune(args: argparse.Namespace) -> int:
|
||||
from tools.checkpoint_manager import prune_checkpoints
|
||||
from tools.checkpoint_manager import prune_checkpoints, store_status
|
||||
|
||||
retention_days = args.retention_days
|
||||
max_size_mb = args.max_size_mb
|
||||
delete_orphans = not args.keep_orphans
|
||||
|
||||
if delete_orphans and not args.force:
|
||||
orphans = [
|
||||
p for p in store_status().get("projects", [])
|
||||
if not p.get("exists")
|
||||
]
|
||||
if orphans:
|
||||
print(f"This will permanently delete {len(orphans)} orphan checkpoint "
|
||||
"project(s) whose workdir is not currently reachable:")
|
||||
print()
|
||||
for p in orphans:
|
||||
wd = p.get("workdir") or "(unknown)"
|
||||
print(f" {wd} ({p.get('commits', 0)} commit(s))")
|
||||
print()
|
||||
print("A workdir can be unreachable because the project was deleted,")
|
||||
print("or because an external volume / network share / VPN is down.")
|
||||
print("Pass --keep-orphans to prune stale entries only.")
|
||||
if not _confirm("Delete these orphan projects?"):
|
||||
print("Aborted.")
|
||||
return 1
|
||||
|
||||
print("Pruning checkpoint store…")
|
||||
print(f" retention_days: {retention_days}")
|
||||
print(f" delete_orphans: {not args.keep_orphans}")
|
||||
print(f" delete_orphans: {delete_orphans}")
|
||||
print(f" max_total_size_mb: {max_size_mb}")
|
||||
print()
|
||||
|
||||
result = prune_checkpoints(
|
||||
retention_days=retention_days,
|
||||
delete_orphans=not args.keep_orphans,
|
||||
delete_orphans=delete_orphans,
|
||||
max_total_size_mb=max_size_mb,
|
||||
)
|
||||
print(f"Scanned: {result['scanned']}")
|
||||
|
|
@ -225,6 +246,8 @@ def register_cli(parser: argparse.ArgumentParser) -> None:
|
|||
"per project until total size <= this (default 500)")
|
||||
p_prune.add_argument("--keep-orphans", action="store_true",
|
||||
help="Skip deleting projects whose workdir no longer exists")
|
||||
p_prune.add_argument("-f", "--force", action="store_true",
|
||||
help="Skip the orphan-deletion confirmation prompt")
|
||||
p_prune.set_defaults(func=cmd_prune)
|
||||
|
||||
p_clear = subs.add_parser(
|
||||
|
|
|
|||
|
|
@ -1304,15 +1304,22 @@ DEFAULT_CONFIG = {
|
|||
"max_file_size_mb": 10,
|
||||
# Auto-maintenance: hermes sweeps the checkpoint base at startup
|
||||
# (at most once per ``min_interval_hours``) and:
|
||||
# * deletes project entries whose workdir no longer exists (orphan)
|
||||
# * deletes project entries whose last_touch is older than
|
||||
# ``retention_days``
|
||||
# * GCs the single shared store to reclaim unreachable objects
|
||||
# * enforces ``max_total_size_mb`` across remaining projects
|
||||
# * deletes ``legacy-*`` archives older than ``retention_days``
|
||||
#
|
||||
# NOTE: this automatic sweep never deletes "orphan" entries (workdir
|
||||
# no longer found on disk). A missing workdir at startup is
|
||||
# ambiguous — it can mean the project was deleted, or that an
|
||||
# external volume / network share / VPN is simply not mounted yet —
|
||||
# and this sweep runs unattended, so it must never guess. Orphan
|
||||
# cleanup is only available via the explicit
|
||||
# ``hermes checkpoints prune`` command (add ``--keep-orphans`` to
|
||||
# skip it), where a human is looking at the output.
|
||||
"auto_prune": True,
|
||||
"retention_days": 7,
|
||||
"delete_orphans": True,
|
||||
"min_interval_hours": 24,
|
||||
},
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue