cobra icon indicating copy to clipboard operation
cobra copied to clipboard

Docs needed: how best to handle errors in Run

Open anentropic opened this issue 5 years ago • 10 comments

Reading the documentation on the front page, it is not clear to me what the best practice for raising errors from a command's Run func is?

Looking at usage of cobra in other projects, the way seems to be just to return os.Exit(1) from the body of the Run func. I'm a total Go noob so my instinct is probably wrong, but this didn't feel quite 'right' to me. Wouldn't that bypass any PostRun hooks? They feel like lifecycle events that should always get a chance to run though.

Then I read to the bottom of issue https://github.com/spf13/cobra/issues/67 and I can see some code got merged four years ago adding a RunE hook which returns error. (Forgive me if I missed it but it seems this is not mentioned in the docs anywhere? https://github.com/spf13/cobra#prerun-and-postrun-hooks Are they deprecated?)

This seemed like what I wanted, but the downside is this causes the 'usage' help text to be output. But that's not always appropriate - maybe the user gave perfectly good args to the command but somewhere in the processing of the job an error had to be returned.

So I have gone back to calling os.Exit from the body of Run. In the end this is fine for my use case.

All this is just to say it would be good to have the framework's opinion on how to handle errors expressed in the docs somewhere.

anentropic avatar Jul 19 '19 22:07 anentropic

To suppress the usage, use the SilenceUsage field.

However, I totally agree with you on that it needs some kind of documentation. Currently wondering why PersistentPostRuns are not executed when RunE returns an error. This is, in my opinion, not obvious. And I would like to still run the PersistentPostRun, instead of just exiting.

tommyknows avatar Aug 06 '19 00:08 tommyknows

Any update on this topic please ?

I am also looking at good practices about errors handling in Cobra. The RunE seems to be really good, but the fact that there is nothing on the documentation is confusing, like if it was not good to use it.

fallais avatar Sep 30 '19 16:09 fallais

After reading the Command code for a while, I don't see a really flexible way to decide how to handle errors at runtime. This is the approach we've ended up on:

func main() {
	if err := command.RootCmd.Execute(); err != nil {
		if err != command.SilentErr {
			fmt.Fprintln(os.Stderr, err)
		}
		os.Exit(1)
	}
}
package command

var SilentErr = errors.New("SilentErr")

var RootCmd = &cobra.Command{
	// ...
	SilenceErrors: true,
	SilenceUsage:  true,
}

func init() {
	RootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
		cmd.Println(err)
		cmd.Println(cmd.UsageString())
		return SilentErr
	})
}

After this, we use RunE for all commands.

The features of this approach are:

  • errors in flag parsing are printed together with command usage help;
  • all other errors are handled in main().

Drawbacks:

  • doesn't print command usage help for unknown command "SUBCMD" for "CMD" errors.

Ref. https://github.com/spf13/cobra/issues/340

mislav avatar Oct 31 '19 14:10 mislav

This issue is being marked as stale due to a long period of inactivity

github-actions[bot] avatar Apr 05 '20 00:04 github-actions[bot]

To suppress the usage, use the SilenceUsage field.

However, I totally agree with you on that it needs some kind of documentation. Currently wondering why PersistentPostRuns are not executed when RunE returns an error. This is, in my opinion, not obvious. And I would like to still run the PersistentPostRun, instead of just exiting.

I'd also like to run a PersistentPostRun hook independent of the return (error or success). I didn't find any mention of this in the documentation either.

jeanmorais avatar Jan 19 '22 23:01 jeanmorais

Reading the docs, I thought RunE would set the error code on exit for me. I did not expect it to print usage info.

It is hilarious to have my service exit with an error (because of misconfig), and then, rather than print that error, it prints the usage info.

AWinterman avatar Mar 29 '22 01:03 AWinterman

Different implementations may differ about exit codes to use, presumably thats why its left out of the implementation. Just as easy as it would be to configure a field ExitCodeOnError=1 (just for instance) it is as easy to do an os.Exit(1) as well. That's my take on why its remained that way.

I agree that it wouldn't be obvious that the postRun methods wouldn't be run if an error occurs. That does seem like something that would be useful to be configurable. You'd wan't the error from the RunE to bubble to the next phase but we wouldn't want to change any of the method signatures. I guess the only implementation would be to add another field to the *cobra.Command object that holds any error encountered during the execution.

I've seen that pattern used before; e.g.

type Foo struct{
  err error
}
func (f *Foo) doA(){
  if f.err != nil {
    return // custom behavior if errors have occurred in the past
  }
  doA() // actually continue

Thoughts on that approach?

johnSchnake avatar Mar 29 '22 16:03 johnSchnake

I wanted to bring this up once again to see if anyone had made any progress or had thoughts on how to run PersistentPostRun after a run, despite any errors.

Currently, in both the Pre and Post runs, I have some code running that takes care of some data collection related operations. Because of this, even after errors, I want to make sure that despite what command was run, and whether it succeeded or failed, I want to make sure the application properly cleans up anything that it was doing. These operations are the same for every single subcommand of the root, so I thought that using Pre and Post hooks would be the correct place to define them.

Did anyone succeed in finding workarounds for this? Because I have so many subcommands, it seems like bad design to have each of the subcommands contain some logic to perform this operation.

sacheth003 avatar Jul 26 '22 23:07 sacheth003