diff --git a/cmd/banger/main.go b/cmd/banger/main.go index bee0caa..0719e11 100644 --- a/cmd/banger/main.go +++ b/cmd/banger/main.go @@ -17,9 +17,9 @@ func main() { cmd := cli.NewBangerCommand() if err := cmd.ExecuteContext(ctx); err != nil { - var exitErr interface{ ExitCode() int } + var exitErr cli.ExitCodeError if errors.As(err, &exitErr) { - os.Exit(exitErr.ExitCode()) + os.Exit(exitErr.Code) } fmt.Fprintf(os.Stderr, "banger: %v\n", err) os.Exit(1) diff --git a/internal/cli/banger.go b/internal/cli/banger.go index 78b38c2..3ec07a7 100644 --- a/internal/cli/banger.go +++ b/internal/cli/banger.go @@ -2810,20 +2810,19 @@ func splitVMRunArgs(cmd *cobra.Command, args []string) (pathArgs, commandArgs [] return args[:dash], args[dash:] } -// exitCodeError wraps a remote command's exit status so the CLI's main() -// can propagate it verbatim. Setup errors and other failures stay as -// regular errors. -type exitCodeError struct { +// ExitCodeError wraps a remote command's exit status so the CLI's main() +// can propagate it verbatim. Only errors explicitly wrapped in this +// type get forwarded as process exit codes — plain *exec.ExitError +// values (from unrelated subprocesses like mkfs.ext4) must still +// surface as regular errors so the user sees a message. +type ExitCodeError struct { Code int } -func (e exitCodeError) Error() string { +func (e ExitCodeError) Error() string { return fmt.Sprintf("exit status %d", e.Code) } -// ExitCode exposes the code for callers using errors.As. -func (e exitCodeError) ExitCode() int { return e.Code } - func runVMRun(ctx context.Context, socketPath string, cfg model.DaemonConfig, stdin io.Reader, stdout, stderr io.Writer, params api.VMCreateParams, spec *vmRunRepoSpec, command []string) error { progress := newVMRunProgressRenderer(stderr) vm, err := runVMCreate(ctx, socketPath, stderr, params) @@ -2871,7 +2870,7 @@ func runVMRun(ctx context.Context, socketPath string, cfg model.DaemonConfig, st if err := sshExecFunc(ctx, stdin, stdout, stderr, sshArgs); err != nil { var exitErr *exec.ExitError if errors.As(err, &exitErr) { - return exitCodeError{Code: exitErr.ExitCode()} + return ExitCodeError{Code: exitErr.ExitCode()} } return err } diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index 9ce8947..ae5b0f3 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -1675,9 +1675,9 @@ func TestRunVMRunCommandModePropagatesExitCode(t *testing.T) { nil, []string{"false"}, ) - var exitErr exitCodeError + var exitErr ExitCodeError if !errors.As(err, &exitErr) || exitErr.Code != 7 { - t.Fatalf("runVMRun error = %v, want exitCodeError{7}", err) + t.Fatalf("runVMRun error = %v, want ExitCodeError{7}", err) } if len(sshArgsSeen) == 0 || sshArgsSeen[len(sshArgsSeen)-1] != "false" { t.Fatalf("sshArgsSeen = %v, want trailing command 'false'", sshArgsSeen)