feat(done): implement self-nuke for polecats (self-cleaning model)
When a polecat runs `gt done` with COMPLETED status, it now nukes its own worktree before exiting. This is the self-cleaning model - polecats clean up after themselves, reducing Witness/Deacon cleanup burden. The self-nuke is: - Only attempted for polecats (not Mayor/Witness/Deacon/Refinery) - Only on COMPLETED status (not ESCALATED/DEFERRED) - Non-fatal: if it fails, Witness will handle cleanup Closes: gt-fqcst Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -343,10 +343,27 @@ func runDone(cmd *cobra.Command, args []string) error {
|
|||||||
// Update agent bead state (ZFC: self-report completion)
|
// Update agent bead state (ZFC: self-report completion)
|
||||||
updateAgentStateOnDone(cwd, townRoot, exitType, issueID)
|
updateAgentStateOnDone(cwd, townRoot, exitType, issueID)
|
||||||
|
|
||||||
|
// Self-cleaning: Nuke our own sandbox before exiting (if we're a polecat)
|
||||||
|
// This is the self-cleaning model - polecats clean up after themselves
|
||||||
|
selfNukeAttempted := false
|
||||||
|
if exitType == ExitCompleted {
|
||||||
|
if roleInfo, err := GetRoleWithContext(cwd, townRoot); err == nil && roleInfo.Role == RolePolecat {
|
||||||
|
selfNukeAttempted = true
|
||||||
|
if err := selfNukePolecat(roleInfo, townRoot); err != nil {
|
||||||
|
// Non-fatal: Witness will clean up if we fail
|
||||||
|
style.PrintWarning("self-nuke failed: %v (Witness will clean up)", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s Sandbox nuked\n", style.Bold.Render("✓"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Always exit session - polecats don't stay alive after completion
|
// Always exit session - polecats don't stay alive after completion
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Printf("%s Session exiting (done means gone)\n", style.Bold.Render("→"))
|
fmt.Printf("%s Session exiting (done means gone)\n", style.Bold.Render("→"))
|
||||||
fmt.Printf(" Witness will handle worktree cleanup.\n")
|
if !selfNukeAttempted {
|
||||||
|
fmt.Printf(" Witness will handle worktree cleanup.\n")
|
||||||
|
}
|
||||||
fmt.Printf(" Goodbye!\n")
|
fmt.Printf(" Goodbye!\n")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
|
||||||
@@ -474,3 +491,29 @@ func parseCleanupStatus(s string) polecat.CleanupStatus {
|
|||||||
return polecat.CleanupUnknown
|
return polecat.CleanupUnknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// selfNukePolecat deletes this polecat's worktree (self-cleaning model).
|
||||||
|
// Called by polecats when they complete work via `gt done`.
|
||||||
|
// This is safe because:
|
||||||
|
// 1. Work has been pushed to origin (MR is in queue)
|
||||||
|
// 2. We're about to exit anyway
|
||||||
|
// 3. Unix allows deleting directories while processes run in them
|
||||||
|
func selfNukePolecat(roleInfo RoleInfo, _ string) error {
|
||||||
|
if roleInfo.Role != RolePolecat || roleInfo.Polecat == "" || roleInfo.Rig == "" {
|
||||||
|
return fmt.Errorf("not a polecat: role=%s, polecat=%s, rig=%s", roleInfo.Role, roleInfo.Polecat, roleInfo.Rig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get polecat manager using existing helper
|
||||||
|
mgr, _, err := getPolecatManager(roleInfo.Rig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getting polecat manager: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use nuclear=true since we know we just pushed our work
|
||||||
|
// The branch is pushed, MR is created, we're clean
|
||||||
|
if err := mgr.RemoveWithOptions(roleInfo.Polecat, true, true); err != nil {
|
||||||
|
return fmt.Errorf("removing worktree: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user