Fix: Unknown subcommands now error instead of silently showing help

Parent commands (mol, mail, crew, polecat, etc.) previously showed help
and exited 0 for unknown subcommands like "gt mol foobar". This masked
errors in scripts and confused users.

Added requireSubcommand() helper to root.go and applied it to all parent
commands. Now unknown subcommands properly error with exit code 1.

Example before: gt mol unhook → shows help, exits 0
Example after:  gt mol unhook → "Error: unknown command "unhook"", exits 1

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gastown/crew/joe
2025-12-30 20:52:23 -08:00
committed by Steve Yegge
parent 3d09c679e2
commit df46e75a51
16 changed files with 29 additions and 0 deletions

View File

@@ -24,6 +24,7 @@ var accountCmd = &cobra.Command{
Use: "account", Use: "account",
GroupID: GroupConfig, GroupID: GroupConfig,
Short: "Manage Claude Code accounts", Short: "Manage Claude Code accounts",
RunE: requireSubcommand,
Long: `Manage multiple Claude Code accounts for Gas Town. Long: `Manage multiple Claude Code accounts for Gas Town.
This enables switching between accounts (e.g., personal vs work) with This enables switching between accounts (e.g., personal vs work) with

View File

@@ -37,6 +37,7 @@ var convoyCmd = &cobra.Command{
Use: "convoy", Use: "convoy",
GroupID: GroupWork, GroupID: GroupWork,
Short: "Track batches of work across rigs", Short: "Track batches of work across rigs",
RunE: requireSubcommand,
Long: `Manage convoys - the primary unit for tracking batched work. Long: `Manage convoys - the primary unit for tracking batched work.
A convoy is a persistent tracking unit that monitors related issues across A convoy is a persistent tracking unit that monitors related issues across

View File

@@ -24,6 +24,7 @@ var crewCmd = &cobra.Command{
Use: "crew", Use: "crew",
GroupID: GroupWorkspace, GroupID: GroupWorkspace,
Short: "Manage crew workspaces (user-managed persistent workspaces)", Short: "Manage crew workspaces (user-managed persistent workspaces)",
RunE: requireSubcommand,
Long: `Crew workers are user-managed persistent workspaces within a rig. Long: `Crew workers are user-managed persistent workspaces within a rig.
Unlike polecats which are witness-managed and transient, crew workers are: Unlike polecats which are witness-managed and transient, crew workers are:

View File

@@ -17,6 +17,7 @@ var daemonCmd = &cobra.Command{
Use: "daemon", Use: "daemon",
GroupID: GroupServices, GroupID: GroupServices,
Short: "Manage the Gas Town daemon", Short: "Manage the Gas Town daemon",
RunE: requireSubcommand,
Long: `Manage the Gas Town background daemon. Long: `Manage the Gas Town background daemon.
The daemon is a simple Go process that: The daemon is a simple Go process that:

View File

@@ -26,6 +26,7 @@ var deaconCmd = &cobra.Command{
Aliases: []string{"dea"}, Aliases: []string{"dea"},
GroupID: GroupAgents, GroupID: GroupAgents,
Short: "Manage the Deacon session", Short: "Manage the Deacon session",
RunE: requireSubcommand,
Long: `Manage the Deacon tmux session. Long: `Manage the Deacon tmux session.
The Deacon is the hierarchical health-check orchestrator for Gas Town. The Deacon is the hierarchical health-check orchestrator for Gas Town.

View File

@@ -46,6 +46,7 @@ var mailCmd = &cobra.Command{
Use: "mail", Use: "mail",
GroupID: GroupComm, GroupID: GroupComm,
Short: "Agent messaging system", Short: "Agent messaging system",
RunE: requireSubcommand,
Long: `Send and receive messages between agents. Long: `Send and receive messages between agents.
The mail system allows Mayor, polecats, and the Refinery to communicate. The mail system allows Mayor, polecats, and the Refinery to communicate.

View File

@@ -21,6 +21,7 @@ var mayorCmd = &cobra.Command{
Aliases: []string{"may"}, Aliases: []string{"may"},
GroupID: GroupAgents, GroupID: GroupAgents,
Short: "Manage the Mayor session", Short: "Manage the Mayor session",
RunE: requireSubcommand,
Long: `Manage the Mayor tmux session. Long: `Manage the Mayor tmux session.
The Mayor is the global coordinator for Gas Town, running as a persistent The Mayor is the global coordinator for Gas Town, running as a persistent

View File

@@ -14,6 +14,7 @@ var moleculeCmd = &cobra.Command{
Aliases: []string{"molecule"}, Aliases: []string{"molecule"},
GroupID: GroupWork, GroupID: GroupWork,
Short: "Agent molecule workflow commands", Short: "Agent molecule workflow commands",
RunE: requireSubcommand,
Long: `Agent-specific molecule workflow operations. Long: `Agent-specific molecule workflow operations.
These commands operate on the current agent's hook and attached molecules. These commands operate on the current agent's hook and attached molecules.
@@ -204,6 +205,7 @@ a permanent (but compact) record.`,
var moleculeStepCmd = &cobra.Command{ var moleculeStepCmd = &cobra.Command{
Use: "step", Use: "step",
Short: "Molecule step operations", Short: "Molecule step operations",
RunE: requireSubcommand,
Long: `Commands for working with molecule steps. Long: `Commands for working with molecule steps.
A molecule is a DAG of steps. Each step is a beads issue with the molecule root A molecule is a DAG of steps. Each step is a beads issue with the molecule root

View File

@@ -53,6 +53,7 @@ var mqCmd = &cobra.Command{
Use: "mq", Use: "mq",
GroupID: GroupWork, GroupID: GroupWork,
Short: "Merge queue operations", Short: "Merge queue operations",
RunE: requireSubcommand,
Long: `Manage the merge queue for a rig. Long: `Manage the merge queue for a rig.
The merge queue tracks work branches from polecats waiting to be merged. The merge queue tracks work branches from polecats waiting to be merged.
@@ -168,6 +169,7 @@ Example:
var mqIntegrationCmd = &cobra.Command{ var mqIntegrationCmd = &cobra.Command{
Use: "integration", Use: "integration",
Short: "Manage integration branches for epics", Short: "Manage integration branches for epics",
RunE: requireSubcommand,
Long: `Manage integration branches for batch work on epics. Long: `Manage integration branches for batch work on epics.
Integration branches allow multiple MRs for an epic to target a shared Integration branches allow multiple MRs for an epic to target a shared

View File

@@ -33,6 +33,7 @@ var polecatCmd = &cobra.Command{
Aliases: []string{"cat", "polecats"}, Aliases: []string{"cat", "polecats"},
GroupID: GroupAgents, GroupID: GroupAgents,
Short: "Manage polecats in rigs", Short: "Manage polecats in rigs",
RunE: requireSubcommand,
Long: `Manage polecat lifecycle in rigs. Long: `Manage polecat lifecycle in rigs.
Polecats are worker agents that operate in their own git worktrees. Polecats are worker agents that operate in their own git worktrees.

View File

@@ -25,6 +25,7 @@ var refineryCmd = &cobra.Command{
Aliases: []string{"ref"}, Aliases: []string{"ref"},
GroupID: GroupAgents, GroupID: GroupAgents,
Short: "Manage the merge queue processor", Short: "Manage the merge queue processor",
RunE: requireSubcommand,
Long: `Manage the Refinery merge queue processor for a rig. Long: `Manage the Refinery merge queue processor for a rig.
The Refinery processes merge requests from polecats, merging their work The Refinery processes merge requests from polecats, merging their work

View File

@@ -26,6 +26,7 @@ var rigCmd = &cobra.Command{
Use: "rig", Use: "rig",
GroupID: GroupWorkspace, GroupID: GroupWorkspace,
Short: "Manage rigs in the workspace", Short: "Manage rigs in the workspace",
RunE: requireSubcommand,
Long: `Manage rigs (project containers) in the Gas Town workspace. Long: `Manage rigs (project containers) in the Gas Town workspace.
A rig is a container for managing a project and its agents: A rig is a container for managing a project and its agents:

View File

@@ -2,6 +2,7 @@
package cmd package cmd
import ( import (
"fmt"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -85,3 +86,14 @@ func buildCommandPath(cmd *cobra.Command) string {
} }
return strings.Join(parts, " ") return strings.Join(parts, " ")
} }
// requireSubcommand returns a RunE function for parent commands that require
// a subcommand. Without this, Cobra silently shows help and exits 0 for
// unknown subcommands like "gt mol foobar", masking errors.
func requireSubcommand(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("requires a subcommand\n\nRun '%s --help' for usage", buildCommandPath(cmd))
}
return fmt.Errorf("unknown command %q for %q\n\nRun '%s --help' for available commands",
args[0], buildCommandPath(cmd), buildCommandPath(cmd))
}

View File

@@ -37,6 +37,7 @@ var sessionCmd = &cobra.Command{
Aliases: []string{"sess"}, Aliases: []string{"sess"},
GroupID: GroupAgents, GroupID: GroupAgents,
Short: "Manage polecat sessions", Short: "Manage polecat sessions",
RunE: requireSubcommand,
Long: `Manage tmux sessions for polecats. Long: `Manage tmux sessions for polecats.
Sessions are tmux sessions running Claude for each polecat. Sessions are tmux sessions running Claude for each polecat.

View File

@@ -40,6 +40,7 @@ var swarmCmd = &cobra.Command{
GroupID: GroupWork, GroupID: GroupWork,
Short: "[DEPRECATED] Use 'gt convoy' instead", Short: "[DEPRECATED] Use 'gt convoy' instead",
Deprecated: "Use 'gt convoy' for work tracking. A 'swarm' is now just the ephemeral workers on a convoy.", Deprecated: "Use 'gt convoy' for work tracking. A 'swarm' is now just the ephemeral workers on a convoy.",
RunE: requireSubcommand,
Long: `DEPRECATED: Use 'gt convoy' instead. Long: `DEPRECATED: Use 'gt convoy' instead.
The term "swarm" now refers to the ephemeral set of workers on a convoy's issues, The term "swarm" now refers to the ephemeral set of workers on a convoy's issues,

View File

@@ -29,6 +29,7 @@ var witnessCmd = &cobra.Command{
Use: "witness", Use: "witness",
GroupID: GroupAgents, GroupID: GroupAgents,
Short: "Manage the polecat monitoring agent", Short: "Manage the polecat monitoring agent",
RunE: requireSubcommand,
Long: `Manage the Witness monitoring agent for a rig. Long: `Manage the Witness monitoring agent for a rig.
The Witness monitors polecats for stuck/idle state, nudges polecats The Witness monitors polecats for stuck/idle state, nudges polecats