Remove os.Exit() from library code (gt-fm75)

Refactor to return errors instead of calling os.Exit() directly:
- Add SilentExitError type for commands that signal status via exit code
- Update mail.go runMailPeek() and runMailCheck() to return errors
- Change Execute() to return int exit code instead of calling os.Exit()
- Move os.Exit() call to main() where it belongs

This improves testability, enables graceful shutdown, and follows Go
conventions for library code.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-30 10:53:10 -08:00
parent 7fdee48807
commit 637f38edca
4 changed files with 53 additions and 17 deletions

View File

@@ -1,8 +1,12 @@
// gt is the Gas Town CLI for managing multi-agent workspaces. // gt is the Gas Town CLI for managing multi-agent workspaces.
package main package main
import "github.com/steveyegge/gastown/internal/cmd" import (
"os"
"github.com/steveyegge/gastown/internal/cmd"
)
func main() { func main() {
cmd.Execute() os.Exit(cmd.Execute())
} }

31
internal/cmd/errors.go Normal file
View File

@@ -0,0 +1,31 @@
package cmd
import "fmt"
// SilentExitError signals that the command should exit with a specific code
// without printing an error message. This is used for scripting purposes
// where exit codes convey status (e.g., "no mail" = exit 1).
type SilentExitError struct {
Code int
}
func (e *SilentExitError) Error() string {
return fmt.Sprintf("exit %d", e.Code)
}
// NewSilentExit creates a SilentExitError with the given exit code.
func NewSilentExit(code int) *SilentExitError {
return &SilentExitError{Code: code}
}
// IsSilentExit checks if an error is a SilentExitError and returns its code.
// Returns 0 and false if err is nil or not a SilentExitError.
func IsSilentExit(err error) (int, bool) {
if err == nil {
return 0, false
}
if se, ok := err.(*SilentExitError); ok {
return se.Code, true
}
return 0, false
}

View File

@@ -579,23 +579,20 @@ func runMailPeek(cmd *cobra.Command, args []string) error {
// All mail uses town beads (two-level architecture) // All mail uses town beads (two-level architecture)
workDir, err := findMailWorkDir() workDir, err := findMailWorkDir()
if err != nil { if err != nil {
os.Exit(1) // Silent exit - no workspace return NewSilentExit(1) // Silent exit - no workspace
return nil
} }
// Get mailbox // Get mailbox
router := mail.NewRouter(workDir) router := mail.NewRouter(workDir)
mailbox, err := router.GetMailbox(address) mailbox, err := router.GetMailbox(address)
if err != nil { if err != nil {
os.Exit(1) // Silent exit - can't access mailbox return NewSilentExit(1) // Silent exit - can't access mailbox
return nil
} }
// Get unread messages // Get unread messages
messages, err := mailbox.ListUnread() messages, err := mailbox.ListUnread()
if err != nil || len(messages) == 0 { if err != nil || len(messages) == 0 {
os.Exit(1) // Silent exit - no unread return NewSilentExit(1) // Silent exit - no unread
return nil
} }
// Show first unread message // Show first unread message
@@ -923,12 +920,10 @@ func runMailCheck(cmd *cobra.Command, args []string) error {
// Normal mode // Normal mode
if unread > 0 { if unread > 0 {
fmt.Printf("%s %d unread message(s)\n", style.Bold.Render("📬"), unread) fmt.Printf("%s %d unread message(s)\n", style.Bold.Render("📬"), unread)
os.Exit(0) return NewSilentExit(0)
} else {
fmt.Println("No new mail")
os.Exit(1)
} }
return nil fmt.Println("No new mail")
return NewSilentExit(1)
} }
func runMailThread(cmd *cobra.Command, args []string) error { func runMailThread(cmd *cobra.Command, args []string) error {

View File

@@ -2,7 +2,6 @@
package cmd package cmd
import ( import (
"os"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -29,11 +28,18 @@ across distributed teams of AI agents working on shared codebases.`,
}, },
} }
// Execute runs the root command // Execute runs the root command and returns an exit code.
func Execute() { // The caller (main) should call os.Exit with this code.
func Execute() int {
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
os.Exit(1) // Check for silent exit (scripting commands that signal status via exit code)
if code, ok := IsSilentExit(err); ok {
return code
}
// Other errors already printed by cobra
return 1
} }
return 0
} }
// Command group IDs - used by subcommands to organize help output // Command group IDs - used by subcommands to organize help output