Gotcha: PersistentPostRunE only runs on successful commands in Cobra

Featured image for sharing metadata for article

In my recent post Lessons learned adding OpenTelemetry to a (Cobra) command-line Go tool, I wrote about how you can wire in OpenTelemetry to a command-line tool built with Cobra.

In it, I noted that to do so, you can use the PersistentPreRunE and PersistentPostRunE.

However, this morning my colleague Mario made me aware that it turns out this doesn't end up working, as he saw a lack of traces for commands that errored, when using the RunE function.

This was unexpected, but appears to be somewhat known behaviour.

RunE

We can see this in action with the following code:

package main

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	Use:   "Example",
	PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
		fmt.Println("Inside PersistentPreRunE")
		return nil
	},
	PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
		fmt.Println("Inside PersistentPostRunE")

		return nil
	},
	RunE: func(cmd *cobra.Command, args []string) error {
		fmt.Println("Inside RunE")

		if len(args) > 0 {
			return fmt.Errorf("uhoh, you gave too many arguments, punk")
		}

		return nil
	},
}

func main() {
	err := rootCmd.Execute()
	if err != nil {
		os.Exit(1)
	}
}
go.mod
module example

go 1.23.2

require github.com/spf13/cobra v1.8.1

require (
	github.com/inconshreveable/mousetrap v1.1.0 // indirect
	github.com/spf13/pflag v1.0.5 // indirect
)

When running the command with no arguments:

# when running and not returning an error from the `RunE`
% go run .
Inside PersistentPreRunE
Inside RunE
Inside PersistentPostRunE
# when running and returning an error from `RunE`
% go run . args
Inside PersistentPreRunE
Inside RunE
Error: uhoh, you gave too many arguments, punk
Usage:
  ...

Notice that when there's an error, we don't see the Inside PersistentPostRunE.

It appears that the solution is to use the OnFinalize:

package main

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	Use: "example",
	PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
		fmt.Println("Inside PersistentPreRunE")
		return nil
	},
	RunE: func(cmd *cobra.Command, args []string) error {
		fmt.Println("Inside RunE")

		if len(args) > 0 {
			return fmt.Errorf("uhoh, you gave too many arguments, punk")
		}

		return nil
	},
}

func main() {
	cobra.OnFinalize(func() {
		fmt.Println("Inside OnFinalize")
	})

	err := rootCmd.Execute()
	if err != nil {
		os.Exit(1)
	}
}

Run

This is also true when we use a Run function:

	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Inside RunE")

		if len(args) > 0 {
			cobra.CheckErr(fmt.Errorf("uhoh, you gave too many arguments, punk"))
		}
	},

In this case, we see the following error:

% go run . args
Inside PersistentPreRunE
Inside RunE
Error: uhoh, you gave too many arguments, punk
exit status 1

However, the OnFinalize doesn't seem to apply here, as if we have this code:

package main

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	Use: "example",
	PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
		fmt.Println("Inside PersistentPreRunE")
		return nil
	},
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Inside RunE")

		if len(args) > 0 {
			cobra.CheckErr(fmt.Errorf("uhoh, you gave too many arguments, punk"))
		}
	},
}

func main() {
	cobra.OnFinalize(func() {
		fmt.Println("Inside OnFinalize")
	})

	err := rootCmd.Execute()
	if err != nil {
		os.Exit(1)
	}
}

Then this doesn't call OnFinalize:

$ go run . args
Inside PersistentPreRunE
Inside RunE
Error: uhoh, you gave too many arguments, punk

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the Apache License 2.0.

#blogumentation #go.

This post was filed under articles.

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.