Source code for ralph.cli.commands.cleanup
"""Cleanup command — remove stale parallel worker namespaces after a hard-kill."""
from __future__ import annotations
import shutil
from typing import Annotated
import typer
from ralph.git.operations import find_repo_root
[docs]
def cleanup(
dry_run: Annotated[
bool,
typer.Option("--dry-run", help="List stale namespaces without removing them"),
] = False,
force: Annotated[
bool,
typer.Option("--force", help="Remove without prompting for confirmation"),
] = False,
) -> None:
"""Remove stale per-worker namespaces under .agent/workers/ after a hard-kill.
In same-workspace parallel mode, each worker writes to .agent/workers/<unit_id>/.
These directories are normally cleaned up automatically, but a hard-kill may
leave them behind.
"""
try:
repo_root = find_repo_root()
except Exception as exc:
typer.echo(f"Error: not in a git repository: {exc}", err=True)
raise typer.Exit(1) from exc
workers_dir = repo_root / ".agent" / "workers"
if not workers_dir.exists():
typer.echo("No stale worker namespaces found")
raise typer.Exit(0)
stale = sorted(d.name for d in workers_dir.iterdir() if d.is_dir())
if not stale:
typer.echo("No stale worker namespaces found")
raise typer.Exit(0)
if dry_run:
typer.echo(f"Found {len(stale)} stale worker namespace(s) (dry-run, not removing):")
for unit_id in stale:
typer.echo(f" .agent/workers/{unit_id}")
raise typer.Exit(0)
if not force:
confirmed = typer.confirm(f"Remove {len(stale)} stale worker namespace(s)?")
if not confirmed:
typer.echo("Aborted")
raise typer.Exit(0)
removed = 0
for unit_id in stale:
target = workers_dir / unit_id
shutil.rmtree(target, ignore_errors=True)
removed += 1
typer.echo(f"Removed {removed} stale worker namespace(s)")