feat(mq): auto-cleanup polecats after MR submission
When `gt mq submit` is run from a polecat work branch (polecat/<worker>/<issue>), it now automatically triggers polecat shutdown after submitting the MR. The polecat sends a lifecycle request to its Witness and waits for termination. This eliminates the need for polecats to manually run `gt handoff --shutdown` after completing work - they can just run `gt mq submit` and the cleanup happens automatically. Added `--no-cleanup` flag to disable auto-cleanup when needed (e.g., for submitting multiple MRs or continuing work). Closes gt-tca 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -436,8 +436,8 @@ func submitMRForPolecat() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run gt mq submit
|
// Run gt mq submit --no-cleanup (handoff manages lifecycle itself)
|
||||||
submitCmd := exec.Command("gt", "mq", "submit")
|
submitCmd := exec.Command("gt", "mq", "submit", "--no-cleanup")
|
||||||
submitOutput, err := submitCmd.CombinedOutput()
|
submitOutput, err := submitCmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s", strings.TrimSpace(string(submitOutput)))
|
return fmt.Errorf("%s", strings.TrimSpace(string(submitOutput)))
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ import (
|
|||||||
// MQ command flags
|
// MQ command flags
|
||||||
var (
|
var (
|
||||||
// Submit flags
|
// Submit flags
|
||||||
mqSubmitBranch string
|
mqSubmitBranch string
|
||||||
mqSubmitIssue string
|
mqSubmitIssue string
|
||||||
mqSubmitEpic string
|
mqSubmitEpic string
|
||||||
mqSubmitPriority int
|
mqSubmitPriority int
|
||||||
|
mqSubmitNoCleanup bool
|
||||||
|
|
||||||
// Retry flags
|
// Retry flags
|
||||||
mqRetryNow bool
|
mqRetryNow bool
|
||||||
@@ -62,7 +63,7 @@ var mqSubmitCmd = &cobra.Command{
|
|||||||
Short: "Submit current branch to the merge queue",
|
Short: "Submit current branch to the merge queue",
|
||||||
Long: `Submit the current branch to the merge queue.
|
Long: `Submit the current branch to the merge queue.
|
||||||
|
|
||||||
Creates a merge-request bead that will be processed by the Engineer.
|
Creates a merge-request bead that will be processed by the Refinery.
|
||||||
|
|
||||||
Auto-detection:
|
Auto-detection:
|
||||||
- Branch: current git branch
|
- Branch: current git branch
|
||||||
@@ -79,11 +80,20 @@ Target branch auto-detection:
|
|||||||
|
|
||||||
This ensures batch work on epics automatically flows to integration branches.
|
This ensures batch work on epics automatically flows to integration branches.
|
||||||
|
|
||||||
|
Polecat auto-cleanup:
|
||||||
|
When run from a polecat work branch (polecat/<worker>/<issue>), this command
|
||||||
|
automatically triggers polecat shutdown after submitting the MR. The polecat
|
||||||
|
sends a lifecycle request to its Witness and waits for termination.
|
||||||
|
|
||||||
|
Use --no-cleanup to disable this behavior (e.g., if you want to submit
|
||||||
|
multiple MRs or continue working).
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
gt mq submit # Auto-detect everything
|
gt mq submit # Auto-detect everything + auto-cleanup
|
||||||
gt mq submit --issue gt-abc # Explicit issue
|
gt mq submit --issue gt-abc # Explicit issue
|
||||||
gt mq submit --epic gt-xyz # Target integration branch explicitly
|
gt mq submit --epic gt-xyz # Target integration branch explicitly
|
||||||
gt mq submit --priority 0 # Override priority (P0)`,
|
gt mq submit --priority 0 # Override priority (P0)
|
||||||
|
gt mq submit --no-cleanup # Submit without auto-cleanup`,
|
||||||
RunE: runMqSubmit,
|
RunE: runMqSubmit,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,6 +253,7 @@ func init() {
|
|||||||
mqSubmitCmd.Flags().StringVar(&mqSubmitIssue, "issue", "", "Source issue ID (default: parse from branch name)")
|
mqSubmitCmd.Flags().StringVar(&mqSubmitIssue, "issue", "", "Source issue ID (default: parse from branch name)")
|
||||||
mqSubmitCmd.Flags().StringVar(&mqSubmitEpic, "epic", "", "Target epic's integration branch instead of main")
|
mqSubmitCmd.Flags().StringVar(&mqSubmitEpic, "epic", "", "Target epic's integration branch instead of main")
|
||||||
mqSubmitCmd.Flags().IntVarP(&mqSubmitPriority, "priority", "p", -1, "Override priority (0-4, default: inherit from issue)")
|
mqSubmitCmd.Flags().IntVarP(&mqSubmitPriority, "priority", "p", -1, "Override priority (0-4, default: inherit from issue)")
|
||||||
|
mqSubmitCmd.Flags().BoolVar(&mqSubmitNoCleanup, "no-cleanup", false, "Don't auto-cleanup after submit (for polecats)")
|
||||||
|
|
||||||
// Retry flags
|
// Retry flags
|
||||||
mqRetryCmd.Flags().BoolVar(&mqRetryNow, "now", false, "Immediately process instead of waiting for refinery loop")
|
mqRetryCmd.Flags().BoolVar(&mqRetryNow, "now", false, "Immediately process instead of waiting for refinery loop")
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/steveyegge/gastown/internal/beads"
|
"github.com/steveyegge/gastown/internal/beads"
|
||||||
@@ -165,6 +167,20 @@ func runMqSubmit(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
fmt.Printf(" Priority: P%d\n", priority)
|
fmt.Printf(" Priority: P%d\n", priority)
|
||||||
|
|
||||||
|
// Auto-cleanup for polecats: if this is a polecat branch and cleanup not disabled,
|
||||||
|
// send lifecycle request and wait for termination
|
||||||
|
if worker != "" && !mqSubmitNoCleanup {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("%s Auto-cleanup: polecat work submitted\n", style.Bold.Render("✓"))
|
||||||
|
if err := polecatCleanup(rigName, worker, townRoot); err != nil {
|
||||||
|
// Non-fatal: warn but return success (MR was created)
|
||||||
|
fmt.Printf("%s Could not auto-cleanup: %v\n", style.Warning.Render("Warning:"), err)
|
||||||
|
fmt.Println(style.Dim.Render(" You may need to run 'gt handoff --shutdown' manually"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// polecatCleanup blocks forever waiting for termination, so we never reach here
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,3 +233,53 @@ func detectIntegrationBranch(bd *beads.Beads, g *git.Git, issueID string) (strin
|
|||||||
|
|
||||||
return "", nil // No integration branch found
|
return "", nil // No integration branch found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// polecatCleanup sends a lifecycle shutdown request to the witness and waits for termination.
|
||||||
|
// This is called after a polecat successfully submits an MR.
|
||||||
|
func polecatCleanup(rigName, worker, townRoot string) error {
|
||||||
|
// Send lifecycle request to witness
|
||||||
|
manager := rigName + "/witness"
|
||||||
|
subject := fmt.Sprintf("LIFECYCLE: polecat-%s requesting shutdown", worker)
|
||||||
|
body := fmt.Sprintf(`Lifecycle request from polecat %s.
|
||||||
|
|
||||||
|
Action: shutdown
|
||||||
|
Reason: MR submitted to merge queue
|
||||||
|
Time: %s
|
||||||
|
|
||||||
|
Please verify state and execute lifecycle action.
|
||||||
|
`, worker, time.Now().Format(time.RFC3339))
|
||||||
|
|
||||||
|
// Send via gt mail
|
||||||
|
cmd := exec.Command("gt", "mail", "send", manager,
|
||||||
|
"-s", subject,
|
||||||
|
"-m", body,
|
||||||
|
)
|
||||||
|
cmd.Dir = townRoot
|
||||||
|
|
||||||
|
if out, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
return fmt.Errorf("sending lifecycle request: %w: %s", err, string(out))
|
||||||
|
}
|
||||||
|
fmt.Printf("%s Sent shutdown request to %s\n", style.Bold.Render("✓"), manager)
|
||||||
|
|
||||||
|
// Wait for retirement with periodic status
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("%s Waiting for retirement...\n", style.Dim.Render("◌"))
|
||||||
|
fmt.Println(style.Dim.Render("(Witness will terminate this session)"))
|
||||||
|
|
||||||
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
waitStart := time.Now()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
elapsed := time.Since(waitStart).Round(time.Second)
|
||||||
|
fmt.Printf("%s Still waiting (%v elapsed)...\n", style.Dim.Render("◌"), elapsed)
|
||||||
|
if elapsed >= 2*time.Minute {
|
||||||
|
fmt.Println(style.Dim.Render(" Hint: If witness isn't responding, you may need to:"))
|
||||||
|
fmt.Println(style.Dim.Render(" - Check if witness is running"))
|
||||||
|
fmt.Println(style.Dim.Render(" - Use Ctrl+C to abort and manually exit"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user