Skip to content

Commit ef62f90

Browse files
committed
fix(cli): show error instead of help for unsupported subcommands (#10760)
The coder CLI quietly accepts any subcommand arguments and silently swallows them. Currently: ❯ coder idontexist | head -n5 coder v2.3.3+e491217 USAGE: coder [global-flags] <subcommand> Now help output will not be show but users will be given the command for the help output. ❯ coder idontexist Encountered an error running "coder", see "coder --help" for more information error: unrecognized subcommand "idontexist" ❯ coder iexistbut idontexist Encountered an error running "coder iexistbut", see "coder iexistbut --help" for more information unrecognized subcommand "idontexist"
1 parent b4c0fa8 commit ef62f90

File tree

6 files changed

+60
-12
lines changed

6 files changed

+60
-12
lines changed

cli/help.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,16 @@ var usageWantsArgRe = regexp.MustCompile(`<.*>`)
320320
// output for a given command.
321321
func helpFn() serpent.HandlerFunc {
322322
return func(inv *serpent.Invocation) error {
323+
// Check for invalid subcommands before printing help.
324+
if len(inv.Args) > 0 && !usageWantsArgRe.MatchString(inv.Command.Use) {
325+
_, _ = fmt.Fprintf(inv.Stderr, "---\nerror: unrecognized subcommand %q\n", inv.Args[0])
326+
}
327+
if len(inv.Args) > 0 {
328+
// Return an error so that exit status is non-zero when
329+
// a subcommand is not found.
330+
return xerrors.Errorf("unrecognized subcommand %q", strings.Join(inv.Args, " "))
331+
}
332+
323333
// We use stdout for help and not stderr since there's no straightforward
324334
// way to distinguish between a user error and a help request.
325335
//
@@ -340,9 +350,6 @@ func helpFn() serpent.HandlerFunc {
340350
if err != nil {
341351
return err
342352
}
343-
if len(inv.Args) > 0 && !usageWantsArgRe.MatchString(inv.Command.Use) {
344-
_, _ = fmt.Fprintf(inv.Stderr, "---\nerror: unknown subcommand %q\n", inv.Args[0])
345-
}
346353
return nil
347354
}
348355
}

cli/portforward.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func (r *RootCmd) portForward() *serpent.Command {
7171
if len(specs) == 0 {
7272
err = inv.Command.HelpHandler(inv)
7373
if err != nil {
74-
return xerrors.Errorf("generate help output: %w", err)
74+
return err
7575
}
7676
return xerrors.New("no port-forwards requested")
7777
}

cli/portforward_test.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,7 @@ func TestPortForward_None(t *testing.T) {
4040

4141
err := inv.Run()
4242
require.Error(t, err)
43-
require.ErrorContains(t, err, "no port-forwards")
44-
45-
// Check that the help was printed.
46-
pty.ExpectMatch("port-forward <workspace>")
43+
require.ErrorContains(t, err, "unrecognized subcommand")
4744
}
4845

4946
func TestPortForward(t *testing.T) {

cli/root.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,10 +1196,13 @@ func formatMultiError(from string, multi []error, opts *formatOpts) string {
11961196
// formatter and add it to cliHumanFormatError function.
11971197
func formatRunCommandError(err *serpent.RunCommandError, opts *formatOpts) string {
11981198
var str strings.Builder
1199-
_, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("Encountered an error running %q", err.Cmd.FullName())))
1199+
_, _ = str.WriteString(pretty.Sprint(headLineStyle(),
1200+
fmt.Sprintf(
1201+
`Encountered an error running %q, see "%s --help" for more information`,
1202+
err.Cmd.FullName(), err.Cmd.FullName())))
1203+
_, _ = str.WriteString(pretty.Sprint(headLineStyle(), "\nerror: "))
12001204

12011205
msgString, special := cliHumanFormatError("", err.Err, opts)
1202-
_, _ = str.WriteString("\n")
12031206
if special {
12041207
_, _ = str.WriteString(msgString)
12051208
} else {

cli/root_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,49 @@ func TestCommandHelp(t *testing.T) {
6060

6161
func TestRoot(t *testing.T) {
6262
t.Parallel()
63+
t.Run("MissingRootCommand", func(t *testing.T) {
64+
t.Parallel()
65+
66+
out := new(bytes.Buffer)
67+
68+
inv, _ := clitest.New(t, "idontexist")
69+
inv.Stdout = out
70+
71+
err := inv.Run()
72+
assert.ErrorContains(t, err,
73+
`unrecognized subcommand "idontexist"`)
74+
require.Empty(t, out.String())
75+
})
76+
77+
t.Run("MissingSubcommand", func(t *testing.T) {
78+
t.Parallel()
79+
80+
out := new(bytes.Buffer)
81+
82+
inv, _ := clitest.New(t, "server", "idontexist")
83+
inv.Stdout = out
84+
85+
err := inv.Run()
86+
// subcommand error only when command has subcommands
87+
assert.ErrorContains(t, err,
88+
`unrecognized subcommand "idontexist"`)
89+
require.Empty(t, out.String())
90+
})
91+
92+
t.Run("BadSubcommandArgs", func(t *testing.T) {
93+
t.Parallel()
94+
95+
out := new(bytes.Buffer)
96+
97+
inv, _ := clitest.New(t, "list", "idontexist")
98+
inv.Stdout = out
99+
100+
err := inv.Run()
101+
assert.ErrorContains(t, err,
102+
`wanted no args but got 1 [idontexist]`)
103+
require.Empty(t, out.String())
104+
})
105+
63106
t.Run("Version", func(t *testing.T) {
64107
t.Parallel()
65108

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,6 @@ github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx
214214
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc=
215215
github.com/coder/retry v1.5.1 h1:iWu8YnD8YqHs3XwqrqsjoBTAVqT9ml6z9ViJ2wlMiqc=
216216
github.com/coder/retry v1.5.1/go.mod h1:blHMk9vs6LkoRT9ZHyuZo360cufXEhrxqvEzeMtRGoY=
217-
github.com/coder/serpent v0.4.0 h1:L/itwnCxfhLutxQ2mScP3tH1ro8z8+Kc/iKKyZZxEMk=
218-
github.com/coder/serpent v0.4.0/go.mod h1:Wud83ikZF/ulbdMcEMAwqvkEIQx7+l47+ef69M/arAA=
219217
github.com/coder/serpent v0.4.1-0.20240315163851-a0148c87ea3f h1:nqJ/Mvm+nLI22n5BIYhvSmTZ6CD+MRo/aGVZwVQgr1k=
220218
github.com/coder/serpent v0.4.1-0.20240315163851-a0148c87ea3f/go.mod h1:REkJ5ZFHQUWFTPLExhXYZ1CaHFjxvGNRlLXLdsI08YA=
221219
github.com/coder/ssh v0.0.0-20231128192721-70855dedb788 h1:YoUSJ19E8AtuUFVYBpXuOD6a/zVP3rcxezNsoDseTUw=

0 commit comments

Comments
 (0)