Files
fn_registry/functions/infra/agent_cleanup_worktree.go
T
egutierrez a87c992cd9 feat(infra): agent_launch_worktree + agent_cleanup_worktree Go fns
Two Go functions in functions/infra/ for orchestrating headless Claude
agents inside isolated git worktrees:

- AgentLaunchWorktree(cfg): creates worktree off master, spawns
  claude -p in background, redirects stdout/stderr to LogPath. Falls
  back to echo stub when claude binary missing (CI/test friendly).
  ResetIfExists support for re-runs.

- AgentCleanupWorktree(repo, branch, path, pid): SIGTERM with 1s
  grace then SIGKILL, git worktree remove --force, git branch -D.
  Best-effort: only errors when all three steps fail (idempotent
  cleanup-twice).

Promotes inline bash from .claude/skills/parallel-fix-issues/ and
fn-orquestador to first-class registry functions. Closes issue 0115.

Capability group: agents.
2026-05-18 18:24:08 +02:00

60 lines
1.8 KiB
Go

package infra
import (
"fmt"
"os/exec"
"strings"
"syscall"
"time"
)
// AgentCleanupWorktree tears down a worktree previously created by
// AgentLaunchWorktree: kills the claude PID (SIGTERM, then SIGKILL after 1s),
// removes the git worktree (force) and deletes the branch.
//
// All three steps are best-effort; we only return an error when ALL three
// fail, so callers can call this on partially-initialised runs safely.
//
// Impure: signals processes, runs git, touches the filesystem.
func AgentCleanupWorktree(repoRoot, branch, worktreePath string, pid int) error {
var killErr, wtErr, brErr error
// 1. Kill the process tree if a PID was provided.
if pid > 0 {
if err := syscall.Kill(pid, syscall.SIGTERM); err != nil {
killErr = err
} else {
// Give it ~1s to exit gracefully, then SIGKILL if still alive.
time.Sleep(1 * time.Second)
if alive := syscall.Kill(pid, 0); alive == nil {
if err := syscall.Kill(pid, syscall.SIGKILL); err != nil {
killErr = err
}
}
}
}
// 2. Remove worktree (force).
if worktreePath != "" {
out, err := exec.Command("git", "-C", repoRoot, "worktree", "remove", "--force", worktreePath).CombinedOutput()
if err != nil {
wtErr = fmt.Errorf("worktree remove: %v: %s", err, strings.TrimSpace(string(out)))
}
}
// 3. Delete the branch.
if branch != "" {
out, err := exec.Command("git", "-C", repoRoot, "branch", "-D", branch).CombinedOutput()
if err != nil {
brErr = fmt.Errorf("branch -D: %v: %s", err, strings.TrimSpace(string(out)))
}
}
// Only error out if every requested step failed. Individual failures are
// expected (e.g. cleanup called twice, dangling branch already gone).
if killErr != nil && wtErr != nil && brErr != nil {
return fmt.Errorf("cleanup failed: kill=%v; worktree=%v; branch=%v", killErr, wtErr, brErr)
}
return nil
}