cli: restrict ExitCodeError unwrap to the CLI's own type
main.go previously unwrapped *any* error implementing `ExitCode() int` into the process exit status, which matched *exec.ExitError too. So whenever a CLI command ran a subprocess (mkfs.ext4, debugfs, ssh to a daemon preflight, etc.) and that subprocess failed, the CLI would silently exit with the subprocess's code — no error message printed. Surfaced while bringing up `banger internal make-bundle`: mkfs.ext4 was failing on an undersized ext4 and the user saw only `EXIT=1`. Fix: export the type as `cli.ExitCodeError` and unwrap against the concrete type in main.go. The `ExitCode()` method is gone — only the explicit wrap at the `vm run` command-mode call site produces this error now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bb95a0a273
commit
a7d1a49aca
3 changed files with 12 additions and 13 deletions
|
|
@ -17,9 +17,9 @@ func main() {
|
||||||
|
|
||||||
cmd := cli.NewBangerCommand()
|
cmd := cli.NewBangerCommand()
|
||||||
if err := cmd.ExecuteContext(ctx); err != nil {
|
if err := cmd.ExecuteContext(ctx); err != nil {
|
||||||
var exitErr interface{ ExitCode() int }
|
var exitErr cli.ExitCodeError
|
||||||
if errors.As(err, &exitErr) {
|
if errors.As(err, &exitErr) {
|
||||||
os.Exit(exitErr.ExitCode())
|
os.Exit(exitErr.Code)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "banger: %v\n", err)
|
fmt.Fprintf(os.Stderr, "banger: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
||||||
|
|
@ -2810,20 +2810,19 @@ func splitVMRunArgs(cmd *cobra.Command, args []string) (pathArgs, commandArgs []
|
||||||
return args[:dash], args[dash:]
|
return args[:dash], args[dash:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// exitCodeError wraps a remote command's exit status so the CLI's main()
|
// ExitCodeError wraps a remote command's exit status so the CLI's main()
|
||||||
// can propagate it verbatim. Setup errors and other failures stay as
|
// can propagate it verbatim. Only errors explicitly wrapped in this
|
||||||
// regular errors.
|
// type get forwarded as process exit codes — plain *exec.ExitError
|
||||||
type exitCodeError struct {
|
// values (from unrelated subprocesses like mkfs.ext4) must still
|
||||||
|
// surface as regular errors so the user sees a message.
|
||||||
|
type ExitCodeError struct {
|
||||||
Code int
|
Code int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e exitCodeError) Error() string {
|
func (e ExitCodeError) Error() string {
|
||||||
return fmt.Sprintf("exit status %d", e.Code)
|
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 {
|
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)
|
progress := newVMRunProgressRenderer(stderr)
|
||||||
vm, err := runVMCreate(ctx, socketPath, stderr, params)
|
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 {
|
if err := sshExecFunc(ctx, stdin, stdout, stderr, sshArgs); err != nil {
|
||||||
var exitErr *exec.ExitError
|
var exitErr *exec.ExitError
|
||||||
if errors.As(err, &exitErr) {
|
if errors.As(err, &exitErr) {
|
||||||
return exitCodeError{Code: exitErr.ExitCode()}
|
return ExitCodeError{Code: exitErr.ExitCode()}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1675,9 +1675,9 @@ func TestRunVMRunCommandModePropagatesExitCode(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
[]string{"false"},
|
[]string{"false"},
|
||||||
)
|
)
|
||||||
var exitErr exitCodeError
|
var exitErr ExitCodeError
|
||||||
if !errors.As(err, &exitErr) || exitErr.Code != 7 {
|
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" {
|
if len(sshArgsSeen) == 0 || sshArgsSeen[len(sshArgsSeen)-1] != "false" {
|
||||||
t.Fatalf("sshArgsSeen = %v, want trailing command 'false'", sshArgsSeen)
|
t.Fatalf("sshArgsSeen = %v, want trailing command 'false'", sshArgsSeen)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue