From a7c843f72d6cfca07dc4b303f4c0a8bdfe505076 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Tue, 23 Dec 2025 22:32:12 -0800 Subject: [PATCH] feat: witness closes mol-polecat-arm when polecat cleaned up (gt-59zd.5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add arm closing to witness cleanup: - Add closePolecatArm(name, reason) method that: - Looks up arm ID in handoff state - Closes the arm issue with the given reason - Clears arm ID from handoff state - Call closePolecatArm() at end of cleanupPolecat() This completes the arm lifecycle: created when polecat discovered, closed when polecat cleaned up. The Christmas Ornament pattern now tracks the full polecat lifecycle. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- internal/witness/manager.go | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/internal/witness/manager.go b/internal/witness/manager.go index f2680728..d00ac963 100644 --- a/internal/witness/manager.go +++ b/internal/witness/manager.go @@ -522,6 +522,43 @@ func (m *Manager) ensurePolecatArm(polecatName string) error { return nil } +// closePolecatArm closes the mol-polecat-arm tracking issue for a polecat. +// Called when the polecat is cleaned up (completed, killed, etc.). +func (m *Manager) closePolecatArm(polecatName, reason string) error { + if m.handoffState == nil { + return nil + } + + ws, ok := m.handoffState.WorkerStates[polecatName] + if !ok || ws.ArmID == "" { + return nil // No arm to close + } + + // Close the arm issue + cmd := exec.Command("bd", "close", ws.ArmID, "--reason", reason) + cmd.Dir = m.workDir + + if out, err := cmd.CombinedOutput(); err != nil { + // If already closed, that's fine + if !strings.Contains(string(out), "already closed") { + return fmt.Errorf("closing arm %s: %s", ws.ArmID, string(out)) + } + } + + fmt.Printf(" Closed arm %s: %s\n", ws.ArmID, reason) + + // Clear the arm ID from handoff state + ws.ArmID = "" + m.handoffState.WorkerStates[polecatName] = ws + + // Persist the updated handoff state + if err := m.saveHandoffState(m.handoffState); err != nil { + return fmt.Errorf("saving handoff state: %w", err) + } + + return nil +} + // checkAndProcess performs health check, shutdown processing, and auto-spawn. func (m *Manager) checkAndProcess(w *Witness) { // Perform health check @@ -1314,6 +1351,11 @@ func (m *Manager) cleanupPolecat(polecatName string) error { fmt.Printf(" Warning: failed to delete branch: %v\n", err) } + // 5. Close the tracking arm (if it exists) + if err := m.closePolecatArm(polecatName, "polecat cleaned up"); err != nil { + fmt.Printf(" Warning: failed to close arm: %v\n", err) + } + return nil }