feat(rig): Add gt rig stop command for multi-rig shutdown
Add new `gt rig stop <rig>...` command that supports stopping multiple rigs with the same shutdown semantics as `gt rig shutdown`: - Stops all polecat sessions - Stops the refinery (if running) - Stops the witness (if running) - Checks for uncommitted work before shutdown (unless --nuclear) Includes --force flag for immediate shutdown and --nuclear flag to bypass safety checks. (gt-lhitf) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -195,6 +195,34 @@ Examples:
|
||||
RunE: runRigStatus,
|
||||
}
|
||||
|
||||
var rigStopCmd = &cobra.Command{
|
||||
Use: "stop <rig>...",
|
||||
Short: "Stop one or more rigs (shutdown semantics)",
|
||||
Long: `Stop all agents in one or more rigs.
|
||||
|
||||
This command is similar to 'gt rig shutdown' but supports multiple rigs.
|
||||
For each rig, it gracefully shuts down:
|
||||
- All polecat sessions
|
||||
- The refinery (if running)
|
||||
- The witness (if running)
|
||||
|
||||
Before shutdown, checks all polecats for uncommitted work:
|
||||
- Uncommitted changes (modified/untracked files)
|
||||
- Stashes
|
||||
- Unpushed commits
|
||||
|
||||
Use --force to skip graceful shutdown and kill immediately.
|
||||
Use --nuclear to bypass ALL safety checks (will lose work!).
|
||||
|
||||
Examples:
|
||||
gt rig stop gastown
|
||||
gt rig stop gastown beads
|
||||
gt rig stop --force gastown beads
|
||||
gt rig stop --nuclear gastown # DANGER: loses uncommitted work`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: runRigStop,
|
||||
}
|
||||
|
||||
// Flags
|
||||
var (
|
||||
rigAddPrefix string
|
||||
@@ -205,6 +233,8 @@ var (
|
||||
rigResetRole string
|
||||
rigShutdownForce bool
|
||||
rigShutdownNuclear bool
|
||||
rigStopForce bool
|
||||
rigStopNuclear bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -218,6 +248,7 @@ func init() {
|
||||
rigCmd.AddCommand(rigShutdownCmd)
|
||||
rigCmd.AddCommand(rigStartCmd)
|
||||
rigCmd.AddCommand(rigStatusCmd)
|
||||
rigCmd.AddCommand(rigStopCmd)
|
||||
|
||||
rigAddCmd.Flags().StringVar(&rigAddPrefix, "prefix", "", "Beads issue prefix (default: derived from name)")
|
||||
|
||||
@@ -231,6 +262,9 @@ func init() {
|
||||
rigShutdownCmd.Flags().BoolVar(&rigShutdownNuclear, "nuclear", false, "DANGER: Bypass ALL safety checks (loses uncommitted work!)")
|
||||
|
||||
rigRebootCmd.Flags().BoolVarP(&rigShutdownForce, "force", "f", false, "Force immediate shutdown during reboot")
|
||||
|
||||
rigStopCmd.Flags().BoolVarP(&rigStopForce, "force", "f", false, "Force immediate shutdown")
|
||||
rigStopCmd.Flags().BoolVar(&rigStopNuclear, "nuclear", false, "DANGER: Bypass ALL safety checks (loses uncommitted work!)")
|
||||
}
|
||||
|
||||
func runRigAdd(cmd *cobra.Command, args []string) error {
|
||||
@@ -1029,3 +1063,132 @@ func runRigStatus(cmd *cobra.Command, args []string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runRigStop(cmd *cobra.Command, args []string) error {
|
||||
// Find workspace
|
||||
townRoot, err := workspace.FindFromCwdOrError()
|
||||
if err != nil {
|
||||
return fmt.Errorf("not in a Gas Town workspace: %w", err)
|
||||
}
|
||||
|
||||
// Load rigs config
|
||||
rigsPath := filepath.Join(townRoot, "mayor", "rigs.json")
|
||||
rigsConfig, err := config.LoadRigsConfig(rigsPath)
|
||||
if err != nil {
|
||||
rigsConfig = &config.RigsConfig{Rigs: make(map[string]config.RigEntry)}
|
||||
}
|
||||
|
||||
g := git.NewGit(townRoot)
|
||||
rigMgr := rig.NewManager(townRoot, rigsConfig, g)
|
||||
|
||||
// Track results
|
||||
var succeeded []string
|
||||
var failed []string
|
||||
|
||||
// Process each rig
|
||||
for _, rigName := range args {
|
||||
r, err := rigMgr.GetRig(rigName)
|
||||
if err != nil {
|
||||
fmt.Printf("%s Rig '%s' not found\n", style.Warning.Render("⚠"), rigName)
|
||||
failed = append(failed, rigName)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check all polecats for uncommitted work (unless nuclear)
|
||||
if !rigStopNuclear {
|
||||
polecatGit := git.NewGit(r.Path)
|
||||
polecatMgr := polecat.NewManager(r, polecatGit)
|
||||
polecats, err := polecatMgr.List()
|
||||
if err == nil && len(polecats) > 0 {
|
||||
var problemPolecats []struct {
|
||||
name string
|
||||
status *git.UncommittedWorkStatus
|
||||
}
|
||||
|
||||
for _, p := range polecats {
|
||||
pGit := git.NewGit(p.ClonePath)
|
||||
status, err := pGit.CheckUncommittedWork()
|
||||
if err == nil && !status.Clean() {
|
||||
problemPolecats = append(problemPolecats, struct {
|
||||
name string
|
||||
status *git.UncommittedWorkStatus
|
||||
}{p.Name, status})
|
||||
}
|
||||
}
|
||||
|
||||
if len(problemPolecats) > 0 {
|
||||
fmt.Printf("\n%s Cannot stop %s - polecats have uncommitted work:\n", style.Warning.Render("⚠"), rigName)
|
||||
for _, pp := range problemPolecats {
|
||||
fmt.Printf(" %s: %s\n", style.Bold.Render(pp.name), pp.status.String())
|
||||
}
|
||||
failed = append(failed, rigName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Stopping rig %s...\n", style.Bold.Render(rigName))
|
||||
|
||||
var errors []string
|
||||
|
||||
// 1. Stop all polecat sessions
|
||||
t := tmux.NewTmux()
|
||||
sessMgr := session.NewManager(t, r)
|
||||
infos, err := sessMgr.List()
|
||||
if err == nil && len(infos) > 0 {
|
||||
fmt.Printf(" Stopping %d polecat session(s)...\n", len(infos))
|
||||
if err := sessMgr.StopAll(rigStopForce); err != nil {
|
||||
errors = append(errors, fmt.Sprintf("polecat sessions: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Stop the refinery
|
||||
refMgr := refinery.NewManager(r)
|
||||
refStatus, err := refMgr.Status()
|
||||
if err == nil && refStatus.State == refinery.StateRunning {
|
||||
fmt.Printf(" Stopping refinery...\n")
|
||||
if err := refMgr.Stop(); err != nil {
|
||||
errors = append(errors, fmt.Sprintf("refinery: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Stop the witness
|
||||
witMgr := witness.NewManager(r)
|
||||
witStatus, err := witMgr.Status()
|
||||
if err == nil && witStatus.State == witness.StateRunning {
|
||||
fmt.Printf(" Stopping witness...\n")
|
||||
if err := witMgr.Stop(); err != nil {
|
||||
errors = append(errors, fmt.Sprintf("witness: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
fmt.Printf("%s Some agents in %s failed to stop:\n", style.Warning.Render("⚠"), rigName)
|
||||
for _, e := range errors {
|
||||
fmt.Printf(" - %s\n", e)
|
||||
}
|
||||
failed = append(failed, rigName)
|
||||
} else {
|
||||
fmt.Printf("%s Rig %s stopped\n", style.Success.Render("✓"), rigName)
|
||||
succeeded = append(succeeded, rigName)
|
||||
}
|
||||
}
|
||||
|
||||
// Summary
|
||||
if len(args) > 1 {
|
||||
fmt.Println()
|
||||
if len(succeeded) > 0 {
|
||||
fmt.Printf("%s Stopped: %s\n", style.Success.Render("✓"), strings.Join(succeeded, ", "))
|
||||
}
|
||||
if len(failed) > 0 {
|
||||
fmt.Printf("%s Failed: %s\n", style.Warning.Render("⚠"), strings.Join(failed, ", "))
|
||||
fmt.Printf("\nUse %s to force shutdown (DANGER: will lose work!)\n", style.Bold.Render("--nuclear"))
|
||||
return fmt.Errorf("some rigs failed to stop")
|
||||
}
|
||||
} else if len(failed) > 0 {
|
||||
fmt.Printf("\nUse %s to force shutdown (DANGER: will lose work!)\n", style.Bold.Render("--nuclear"))
|
||||
return fmt.Errorf("rig failed to stop")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user