a87c992cd9
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.
60 lines
1.8 KiB
Go
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
|
|
}
|