fix(plan): address review findings on worktree sandbox cleanup
CI / status-check (pull_request) Blocked by required conditions
CI / helm (pull_request) Successful in 29s
CI / build (pull_request) Successful in 3m47s
CI / lint (pull_request) Successful in 3m57s
CI / quality (pull_request) Successful in 4m14s
CI / unit_tests (pull_request) Failing after 4m18s
CI / typecheck (pull_request) Successful in 4m35s
CI / security (pull_request) Successful in 4m46s
CI / coverage (pull_request) Waiting to run
CI / docker (pull_request) Has been skipped
CI / push-validation (pull_request) Successful in 22s
CI / e2e_tests (pull_request) Successful in 6m54s
CI / integration_tests (pull_request) Successful in 6m58s

- Move get_container and GitWorktreeSandbox imports to module top level
  (G2: satisfies top-of-file import requirement)
- Replace bare 'except Exception' with specific NotFoundError,
  CleverAgentsError, SQLAlchemyError catches with structlog logging
  (G3: proper error handling)
- Add input validation: empty/whitespace plan_id returns early with
  warning log (G4: guard against invalid input)
- cleanup_stale() now tracks branch_deleted flag and logs partial
  cleanup warning when branch deletion fails (G5: accurate reporting)
- Check off all issue #9230 subtasks (G7: process compliance)

ISSUES CLOSED: #9230
This commit is contained in:
2026-04-20 14:18:04 +00:00
parent b907ccd9f8
commit 770dee16b8
2 changed files with 45 additions and 9 deletions
+35 -7
View File
@@ -31,6 +31,7 @@ from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING, Annotated, Any, Literal, cast
import structlog
import typer
from rich.console import Console
from rich.panel import Panel
@@ -39,13 +40,18 @@ from rich.table import Table
from sqlalchemy.exc import SQLAlchemyError
from cleveragents.a2a.models import A2aRequest
from cleveragents.application.container import get_container
from cleveragents.cli.formatting import OutputFormat, format_output
from cleveragents.core.exceptions import (
CleverAgentsError,
NotFoundError,
PlanError,
ValidationError,
)
from cleveragents.domain.models.core.plan import PlanPhase, ProcessingState
from cleveragents.infrastructure.sandbox.git_worktree import (
GitWorktreeSandbox,
)
# Regex for validating namespaced actor names (namespace/name format)
_NAMESPACED_ACTOR_RE = re.compile(r"^[a-z][a-z0-9-]*/[a-z][a-z0-9._-]*$")
@@ -1349,6 +1355,9 @@ def _get_lifecycle_service():
return container.plan_lifecycle_service()
_cleanup_logger = structlog.get_logger("cleveragents.cli.commands.plan.cleanup")
def _cleanup_sandbox_for_plan(
plan_id: str,
service: PlanLifecycleService,
@@ -1361,19 +1370,33 @@ def _cleanup_sandbox_for_plan(
Used by ``plan cancel`` to prevent resource leaks.
"""
from cleveragents.application.container import get_container
from cleveragents.infrastructure.sandbox.git_worktree import (
GitWorktreeSandbox,
)
if not plan_id or not plan_id.strip():
_cleanup_logger.warning("cleanup_sandbox.skipped", reason="empty plan_id")
return
container = get_container()
plan = service.get_plan(plan_id)
try:
plan = service.get_plan(plan_id)
except (NotFoundError, CleverAgentsError) as exc:
_cleanup_logger.warning(
"cleanup_sandbox.plan_not_found",
plan_id=plan_id,
error=str(exc),
)
return
project_names = [pl.project_name for pl in getattr(plan, "project_links", [])]
for project_name in project_names:
try:
project = container.namespaced_project_repo().get(project_name)
except Exception:
except (NotFoundError, CleverAgentsError, SQLAlchemyError) as exc:
_cleanup_logger.debug(
"cleanup_sandbox.project_lookup_failed",
project_name=project_name,
error=str(exc),
)
continue
if project is None:
continue
@@ -1382,7 +1405,12 @@ def _cleanup_sandbox_for_plan(
resource = container.resource_registry_service().show_resource(
lr.resource_id,
)
except Exception:
except (NotFoundError, CleverAgentsError, SQLAlchemyError) as exc:
_cleanup_logger.debug(
"cleanup_sandbox.resource_lookup_failed",
resource_id=lr.resource_id,
error=str(exc),
)
continue
if (
resource.resource_type_name not in ("git-checkout", "git")
@@ -226,9 +226,11 @@ class GitWorktreeSandbox:
branch_name,
)
branch_deleted = True
try:
_run_git(["branch", "-D", branch_name], cwd=repo_path)
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
branch_deleted = False
logger.warning(
"Failed to delete stale branch %s",
branch_name,
@@ -240,8 +242,14 @@ class GitWorktreeSandbox:
):
_run_git(["worktree", "prune"], cwd=repo_path)
logger.info("Stale sandbox branch cleaned up: branch=%s", branch_name)
return True
if branch_deleted:
logger.info("Stale sandbox branch cleaned up: branch=%s", branch_name)
else:
logger.warning(
"Partial cleanup: worktree removed but branch persists: branch=%s",
branch_name,
)
return True # Branch was found; cleanup attempted (even if partial)
# -- protocol methods ----------------------------------------------------