diff --git a/internal/cmd/account.go b/internal/cmd/account.go index 07882b4d..5a75833d 100644 --- a/internal/cmd/account.go +++ b/internal/cmd/account.go @@ -24,6 +24,7 @@ var accountCmd = &cobra.Command{ Use: "account", GroupID: GroupConfig, Short: "Manage Claude Code accounts", + RunE: requireSubcommand, Long: `Manage multiple Claude Code accounts for Gas Town. This enables switching between accounts (e.g., personal vs work) with diff --git a/internal/cmd/convoy.go b/internal/cmd/convoy.go index 5c541e61..57e33f8e 100644 --- a/internal/cmd/convoy.go +++ b/internal/cmd/convoy.go @@ -37,6 +37,7 @@ var convoyCmd = &cobra.Command{ Use: "convoy", GroupID: GroupWork, Short: "Track batches of work across rigs", + RunE: requireSubcommand, Long: `Manage convoys - the primary unit for tracking batched work. A convoy is a persistent tracking unit that monitors related issues across diff --git a/internal/cmd/crew.go b/internal/cmd/crew.go index 1fac5491..0d9f4761 100644 --- a/internal/cmd/crew.go +++ b/internal/cmd/crew.go @@ -24,6 +24,7 @@ var crewCmd = &cobra.Command{ Use: "crew", GroupID: GroupWorkspace, Short: "Manage crew workspaces (user-managed persistent workspaces)", + RunE: requireSubcommand, Long: `Crew workers are user-managed persistent workspaces within a rig. Unlike polecats which are witness-managed and transient, crew workers are: diff --git a/internal/cmd/daemon.go b/internal/cmd/daemon.go index 7cdcedb3..175cbf5b 100644 --- a/internal/cmd/daemon.go +++ b/internal/cmd/daemon.go @@ -17,6 +17,7 @@ var daemonCmd = &cobra.Command{ Use: "daemon", GroupID: GroupServices, Short: "Manage the Gas Town daemon", + RunE: requireSubcommand, Long: `Manage the Gas Town background daemon. The daemon is a simple Go process that: diff --git a/internal/cmd/deacon.go b/internal/cmd/deacon.go index 9c6f0a83..33841d2e 100644 --- a/internal/cmd/deacon.go +++ b/internal/cmd/deacon.go @@ -26,6 +26,7 @@ var deaconCmd = &cobra.Command{ Aliases: []string{"dea"}, GroupID: GroupAgents, Short: "Manage the Deacon session", + RunE: requireSubcommand, Long: `Manage the Deacon tmux session. The Deacon is the hierarchical health-check orchestrator for Gas Town. diff --git a/internal/cmd/mail.go b/internal/cmd/mail.go index 8b15f12e..773037e0 100644 --- a/internal/cmd/mail.go +++ b/internal/cmd/mail.go @@ -46,6 +46,7 @@ var mailCmd = &cobra.Command{ Use: "mail", GroupID: GroupComm, Short: "Agent messaging system", + RunE: requireSubcommand, Long: `Send and receive messages between agents. The mail system allows Mayor, polecats, and the Refinery to communicate. diff --git a/internal/cmd/mayor.go b/internal/cmd/mayor.go index dff67c82..82557061 100644 --- a/internal/cmd/mayor.go +++ b/internal/cmd/mayor.go @@ -21,6 +21,7 @@ var mayorCmd = &cobra.Command{ Aliases: []string{"may"}, GroupID: GroupAgents, Short: "Manage the Mayor session", + RunE: requireSubcommand, Long: `Manage the Mayor tmux session. The Mayor is the global coordinator for Gas Town, running as a persistent diff --git a/internal/cmd/molecule.go b/internal/cmd/molecule.go index 2cc40d69..3344e252 100644 --- a/internal/cmd/molecule.go +++ b/internal/cmd/molecule.go @@ -14,6 +14,7 @@ var moleculeCmd = &cobra.Command{ Aliases: []string{"molecule"}, GroupID: GroupWork, Short: "Agent molecule workflow commands", + RunE: requireSubcommand, Long: `Agent-specific molecule workflow operations. 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{ Use: "step", Short: "Molecule step operations", + RunE: requireSubcommand, Long: `Commands for working with molecule steps. A molecule is a DAG of steps. Each step is a beads issue with the molecule root diff --git a/internal/cmd/mq.go b/internal/cmd/mq.go index 3bd42550..2d97bdce 100644 --- a/internal/cmd/mq.go +++ b/internal/cmd/mq.go @@ -53,6 +53,7 @@ var mqCmd = &cobra.Command{ Use: "mq", GroupID: GroupWork, Short: "Merge queue operations", + RunE: requireSubcommand, Long: `Manage the merge queue for a rig. The merge queue tracks work branches from polecats waiting to be merged. @@ -168,6 +169,7 @@ Example: var mqIntegrationCmd = &cobra.Command{ Use: "integration", Short: "Manage integration branches for epics", + RunE: requireSubcommand, Long: `Manage integration branches for batch work on epics. Integration branches allow multiple MRs for an epic to target a shared diff --git a/internal/cmd/polecat.go b/internal/cmd/polecat.go index 77f4e580..0ad3f7b3 100644 --- a/internal/cmd/polecat.go +++ b/internal/cmd/polecat.go @@ -33,6 +33,7 @@ var polecatCmd = &cobra.Command{ Aliases: []string{"cat", "polecats"}, GroupID: GroupAgents, Short: "Manage polecats in rigs", + RunE: requireSubcommand, Long: `Manage polecat lifecycle in rigs. Polecats are worker agents that operate in their own git worktrees. diff --git a/internal/cmd/refinery.go b/internal/cmd/refinery.go index 75d75043..27aeaf63 100644 --- a/internal/cmd/refinery.go +++ b/internal/cmd/refinery.go @@ -25,6 +25,7 @@ var refineryCmd = &cobra.Command{ Aliases: []string{"ref"}, GroupID: GroupAgents, Short: "Manage the merge queue processor", + RunE: requireSubcommand, Long: `Manage the Refinery merge queue processor for a rig. The Refinery processes merge requests from polecats, merging their work diff --git a/internal/cmd/rig.go b/internal/cmd/rig.go index fb1d68cc..52fd60a1 100644 --- a/internal/cmd/rig.go +++ b/internal/cmd/rig.go @@ -26,6 +26,7 @@ var rigCmd = &cobra.Command{ Use: "rig", GroupID: GroupWorkspace, Short: "Manage rigs in the workspace", + RunE: requireSubcommand, Long: `Manage rigs (project containers) in the Gas Town workspace. A rig is a container for managing a project and its agents: diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 75ae1a11..2a95bdb0 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -2,6 +2,7 @@ package cmd import ( + "fmt" "strings" "github.com/spf13/cobra" @@ -85,3 +86,14 @@ func buildCommandPath(cmd *cobra.Command) string { } 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)) +} diff --git a/internal/cmd/session.go b/internal/cmd/session.go index 79ef57fd..6d7ff4e5 100644 --- a/internal/cmd/session.go +++ b/internal/cmd/session.go @@ -37,6 +37,7 @@ var sessionCmd = &cobra.Command{ Aliases: []string{"sess"}, GroupID: GroupAgents, Short: "Manage polecat sessions", + RunE: requireSubcommand, Long: `Manage tmux sessions for polecats. Sessions are tmux sessions running Claude for each polecat. diff --git a/internal/cmd/swarm.go b/internal/cmd/swarm.go index 67ca6f48..df92fb50 100644 --- a/internal/cmd/swarm.go +++ b/internal/cmd/swarm.go @@ -40,6 +40,7 @@ var swarmCmd = &cobra.Command{ GroupID: GroupWork, Short: "[DEPRECATED] Use 'gt convoy' instead", 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. The term "swarm" now refers to the ephemeral set of workers on a convoy's issues, diff --git a/internal/cmd/witness.go b/internal/cmd/witness.go index bf528c15..34f5df66 100644 --- a/internal/cmd/witness.go +++ b/internal/cmd/witness.go @@ -29,6 +29,7 @@ var witnessCmd = &cobra.Command{ Use: "witness", GroupID: GroupAgents, Short: "Manage the polecat monitoring agent", + RunE: requireSubcommand, Long: `Manage the Witness monitoring agent for a rig. The Witness monitors polecats for stuck/idle state, nudges polecats