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.
This commit is contained in:
2026-05-18 18:24:08 +02:00
parent 4eb4c1cf98
commit 355bcac6c7
4 changed files with 301 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
---
name: agent_cleanup_worktree
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func AgentCleanupWorktree(repoRoot, branch, worktreePath string, pid int) error"
description: "Tear-down de un worktree creado por agent_launch_worktree_go_infra: manda SIGTERM al PID (espera 1s, luego SIGKILL si sigue vivo), corre `git worktree remove --force` y `git branch -D` (best-effort cada uno). Devuelve error SOLO si los tres pasos fallan — fallos individuales son esperados (cleanup doble, rama ya borrada, etc.). PID=0 desactiva el kill (util cuando el proceso ya murio o nunca arranco). Linux/Darwin: usa syscall.Kill. Windows: la funcion compila pero el kill nunca hace nada porque syscall.Kill no existe alli — documentar como skip."
tags: [agents, worktree, cleanup, git, kill]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: ["fmt", "os/exec", "strings", "syscall", "time"]
params:
- name: repoRoot
desc: "path absoluto al repo principal (el que tiene el worktree registrado)."
- name: branch
desc: "nombre de la rama a borrar (ej. auto/0115-foo). Vacio = skip."
- name: worktreePath
desc: "path absoluto al worktree a eliminar. Vacio = skip."
- name: pid
desc: "PID de claude o 0 para saltarse el kill (proceso ya muerto / nunca arranco)."
output: "error nil cuando al menos uno de los tres pasos (kill, worktree remove, branch -D) tuvo exito o se salto. error no-nil solo si los tres fallaron — incluye los tres mensajes para diagnostico."
tested: true
tests:
- "removes worktree dir and branch after launch"
- "tolerates missing worktree/branch (cleanup called twice)"
test_file_path: "functions/infra/agent_cleanup_worktree_test.go"
file_path: "functions/infra/agent_cleanup_worktree.go"
---
## Ejemplo
```go
err := infra.AgentCleanupWorktree(
"/home/lucas/fn_registry",
"auto/0115-worktree-launcher-fn",
"/home/lucas/fn_registry/worktrees/0115-worktree-launcher-fn",
12345, // PID devuelto por AgentLaunchWorktree
)
if err != nil {
log.Printf("cleanup partial failure: %v", err)
}
```
## Cuando usarla
Tras terminar (o abortar) un run lanzado con `agent_launch_worktree_go_infra`. Tambien util en defers de tests para garantizar limpieza: `defer infra.AgentCleanupWorktree(repo, branch, wt, res.PID)`. Si el run sigue corriendo y solo quieres parar el proceso sin tocar git, llama tu mismo a `syscall.Kill(pid, syscall.SIGTERM)` — esta funcion hace mas que eso.
## Gotchas
- **Best-effort por diseño**: cleanup doble no es error. Es deliberado para que `agent_runner_api` pueda llamarla en abort handlers sin meter el sistema en bucle.
- **SIGTERM grace 1s**: si claude tarda mas de 1s en cerrar limpiamente, se mata con SIGKILL — los buffers del log pueden quedar parcialmente escritos. Si necesitas mas grace, fork la funcion.
- **Windows**: `syscall.Kill` no existe en Windows. El codigo compila pero salta el kill silenciosamente. Para Windows real, swap `syscall.Kill` por `os.Process.Kill()` (requiere abrir el proceso primero con `os.FindProcess`).
- **Branch en HEAD del repo principal**: si la rama a borrar es la checked-out branch del repo principal, `git branch -D` falla — pero como worktree elimino ya su HEAD, en la practica nunca pasa con ramas `auto/*`.
- **Worktree con cambios sin commitear**: `--force` los descarta. Si necesitas preservar trabajo, commitea y push antes de llamar.