Repository: bacalhau-project/bacalhau (language: python)

Issue:
Consider consistent cobra.Command construction
Currently some of our commands use the Run field in `cobra.Command` to specify the function to run. They explicitly handle an error and then call util.Fatal.

Others of our commands use the RunE field, which allows the function to return an error, which is then handled in root and calls util.Fatal in one place.

We should introduce some consistency in using RunE so that:

* We are only calling Fatal in one place
* The commands are easier to test as an error is available.


Discussion / hints:

Fix patch:
diff --git a/cmd/cli/agent/alive.go b/cmd/cli/agent/alive.go
index 93199bac50..53ea1f6cd1 100644
--- a/cmd/cli/agent/alive.go
+++ b/cmd/cli/agent/alive.go
@@ -28,22 +28,24 @@ func NewAliveCmd() *cobra.Command {
 		Use:   "alive",
 		Short: "Get the agent's liveness and health info.",
 		Args:  cobra.NoArgs,
-		Run:   o.runAlive,
+		RunE:  o.runAlive,
 	}
 	aliveCmd.Flags().AddFlagSet(cliflags.OutputNonTabularFormatFlags(&o.OutputOpts))
 	return aliveCmd
 }
 
 // Run executes alive command
-func (o *AliveOptions) runAlive(cmd *cobra.Command, _ []string) {
+func (o *AliveOptions) runAlive(cmd *cobra.Command, _ []string) error {
 	ctx := cmd.Context()
 	response, err := util.GetAPIClientV2(cmd).Agent().Alive(ctx)
 	if err != nil {
-		util.Fatal(cmd, fmt.Errorf("could not get server alive: %w", err), 1)
+		return fmt.Errorf("could not get server alive: %w", err)
 	}
 
 	writeErr := output.OutputOneNonTabular(cmd, o.OutputOpts, response)
 	if writeErr != nil {
-		util.Fatal(cmd, fmt.Errorf("failed to write alive: %w", writeErr), 1)
+		return fmt.Errorf("failed to write alive: %w", writeErr)
 	}
+
+	return nil
 }
diff --git a/cmd/cli/agent/node.go b/cmd/cli/agent/node.go
index 4a01f7bad1..4fe10f36c5 100644
--- a/cmd/cli/agent/node.go
+++ b/cmd/cli/agent/node.go
@@ -29,22 +29,24 @@ func NewNodeCmd() *cobra.Command {
 		Use:   "node",
 		Short: "Get the agent's node info.",
 		Args:  cobra.NoArgs,
-		Run:   o.runNode,
+		RunE:  o.runNode,
 	}
 	nodeCmd.Flags().AddFlagSet(cliflags.OutputNonTabularFormatFlags(&o.OutputOpts))
 	return nodeCmd
 }
 
 // Run executes node command
-func (o *NodeOptions) runNode(cmd *cobra.Command, _ []string) {
+func (o *NodeOptions) runNode(cmd *cobra.Command, _ []string) error {
 	ctx := cmd.Context()
 	response, err := util.GetAPIClientV2(cmd).Agent().Node(ctx, &apimodels.GetAgentNodeRequest{})
 	if err != nil {
-		util.Fatal(cmd, fmt.Errorf("could not get server node: %w", err), 1)
+		return fmt.Errorf("could not get server node: %w", err)
 	}
 
 	writeErr := output.OutputOneNonTabular(cmd, o.OutputOpts, response.NodeInfo)
 	if writeErr != nil {
-		util.Fatal(cmd, fmt.Errorf("failed to write node: %w", writeErr), 1)
+		return fmt.Errorf("failed to write node: %w", writeErr)
 	}
+
+	return nil
 }
diff --git a/cmd/cli/agent/version.go b/cmd/cli/agent/version.go
index b53ad854f8..c794afa7fd 100644
--- a/cmd/cli/agent/version.go
+++ b/cmd/cli/agent/version.go
@@ -29,18 +29,18 @@ func NewVersionCmd() *cobra.Command {
 		Use:   "version",
 		Short: "Get the agent version.",
 		Args:  cobra.NoArgs,
-		Run:   oV.runVersion,
+		RunE:  oV.runVersion,
 	}
 	versionCmd.Flags().AddFlagSet(cliflags.OutputNonTabularFormatFlags(&oV.OutputOpts))
 	return versionCmd
 }
 
 // Run executes version command
-func (oV *VersionOptions) runVersion(cmd *cobra.Command, _ []string) {
+func (oV *VersionOptions) runVersion(cmd *cobra.Command, _ []string) error {
 	ctx := cmd.Context()
 	serverVersionResponse, err := util.GetAPIClientV2(cmd).Agent().Version(ctx)
 	if err != nil {
-		util.Fatal(cmd, fmt.Errorf("could not get server version: %w", err), 1)
+		return fmt.Errorf("could not get server version: %w", err)
 	}
 
 	v := serverVersionResponse.BuildVersionInfo
@@ -58,6 +58,8 @@ func (oV *VersionOptions) runVersion(cmd *cobra.Command, _ []string) {
 	}
 
 	if writeErr != nil {
-		util.Fatal(cmd, fmt.Errorf("failed to write version: %w", writeErr), 1)
+		return fmt.Errorf("failed to write version: %w", writeErr)
 	}
+
+	return nil
 }
diff --git a/cmd/cli/create/create.go b/cmd/cli/create/create.go
index 45313ae93d..2a9c1bf5b4 100644
--- a/cmd/cli/create/create.go
+++ b/cmd/cli/create/create.go
@@ -67,10 +67,8 @@ func NewCmd() *cobra.Command {
 		Args:     cobra.MinimumNArgs(0),
 		PreRunE:  hook.RemoteCmdPreRunHooks,
 		PostRunE: hook.RemoteCmdPostRunHooks,
-		Run: func(cmd *cobra.Command, cmdArgs []string) {
-			if err := create(cmd, cmdArgs, OC); err != nil {
-				util.Fatal(cmd, err, 1)
-			}
+		RunE: func(cmd *cobra.Command, cmdArgs []string) error {
+			return create(cmd, cmdArgs, OC)
 		},
 	}
 
diff --git a/cmd/cli/describe/describe.go b/cmd/cli/describe/describe.go
index b737bb080a..88fbdeee3b 100644
--- a/cmd/cli/describe/describe.go
+++ b/cmd/cli/describe/describe.go
@@ -59,10 +59,8 @@ func NewCmd() *cobra.Command {
 		Args:     cobra.ExactArgs(1),
 		PreRunE:  hook.RemoteCmdPreRunHooks,
 		PostRunE: hook.RemoteCmdPostRunHooks,
-		Run: func(cmd *cobra.Command, cmdArgs []string) { // nolintunparam // incorrectly suggesting unused
-			if err := describe(cmd, cmdArgs, OD); err != nil {
-				util.Fatal(cmd, err, 1)
-			}
+		RunE: func(cmd *cobra.Command, cmdArgs []string) error {
+			return describe(cmd, cmdArgs, OD)
 		},
 	}
 
diff --git a/cmd/cli/devstack/devstack.go b/cmd/cli/devstack/devstack.go
index 42310e1b02..ecd854c50c 100644
--- a/cmd/cli/devstack/devstack.go
+++ b/cmd/cli/devstack/devstack.go
@@ -81,15 +81,11 @@ func NewCmd() *cobra.Command {
 		Short:   "Start a cluster of bacalhau nodes for testing and development",
 		Long:    devStackLong,
 		Example: devstackExample,
-		PreRun: func(cmd *cobra.Command, _ []string) {
-			if err := configflags.BindFlags(cmd, devstackFlags); err != nil {
-				util.Fatal(cmd, err, 1)
-			}
+		PreRunE: func(cmd *cobra.Command, _ []string) error {
+			return configflags.BindFlags(cmd, devstackFlags)
 		},
-		Run: func(cmd *cobra.Command, _ []string) {
-			if err := runDevstack(cmd, ODs, IsNoop); err != nil {
-				util.Fatal(cmd, err, 1)
-			}
+		RunE: func(cmd *cobra.Command, _ []string) error {
+			return runDevstack(cmd, ODs, IsNoop)
 		},
 	}
 
diff --git a/cmd/cli/docker/docker_run.go b/cmd/cli/docker/docker_run.go
index f9c5cef7a2..ec60c78cdf 100644
--- a/cmd/cli/docker/docker_run.go
+++ b/cmd/cli/docker/docker_run.go
@@ -105,10 +105,8 @@ func newDockerRunCmd() *cobra.Command { //nolint:funlen
 		Args:     cobra.MinimumNArgs(1),
 		PreRunE:  hook.Chain(hook.RemoteCmdPreRunHooks, configflags.PreRun(dockerRunFlags)),
 		PostRunE: hook.RemoteCmdPostRunHooks,
-		Run: func(cmd *cobra.Command, cmdArgs []string) {
-			if err := dockerRun(cmd, cmdArgs, opts); err != nil {
-				util.Fatal(cmd, err, 1)
-			}
+		RunE: func(cmd *cobra.Command, cmdArgs []string) error {
+			return dockerRun(cmd, cmdArgs, opts)
 		},
 	}
 
diff --git a/cmd/cli/exec/exec.go b/cmd/cli/exec/exec.go
index 9e9e944127..d3039b3a39 100644
--- a/cmd/cli/exec/exec.go
+++ b/cmd/cli/exec/exec.go
@@ -80,15 +80,13 @@ func NewCmdWithOptions(options *ExecOptions) *cobra.Command {
 		PreRunE:            hook.RemoteCmdPreRunHooks,
 		PostRunE:           hook.RemoteCmdPostRunHooks,
 		FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
-		Run: func(cmd *cobra.Command, cmdArgs []string) {
+		RunE: func(cmd *cobra.Command, cmdArgs []string) error {
 			// Find the unknown arguments from the original args.  We only want to find the
 			// flags that are unknown. We will only support the long form for custom
 			// job types as we will want to use them as keys in template completions.
 			unknownArgs := ExtractUnknownArgs(cmd.Flags(), os.Args[1:])
 
-			if err := exec(cmd, cmdArgs, unknownArgs, options); err != nil {
-				util.Fatal(cmd, err, 1)
-			}
+			return exec(cmd, cmdArgs, unknownArgs, options)
 		},
 	}
 
diff --git a/cmd/cli/get/get.go b/cmd/cli/get/get.go
index 0978ce187e..381fd2dd33 100644
--- a/cmd/cli/get/get.go
+++ b/cmd/cli/get/get.go
@@ -55,10 +55,8 @@ func NewCmd() *cobra.Command {
 		Args:     cobra.ExactArgs(1),
 		PreRunE:  hook.Chain(hook.RemoteCmdPreRunHooks, configflags.PreRun(getFlags)),
 		PostRunE: hook.RemoteCmdPostRunHooks,
-		Run: func(cmd *cobra.Command, cmdArgs []string) {
-			if err := get(cmd, cmdArgs, OG); err != nil {
-				util.Fatal(cmd, err, 1)
-			}
+		RunE: func(cmd *cobra.Command, cmdArgs []string) error {
+			return get(cmd, cmdArgs, OG)
 		},
 	}
 
diff --git a/cmd/cli/id/id.go b/cmd/cli/id/id.go
index b914d5a8d4..09b9e1b097 100644
--- a/cmd/cli/id/id.go
+++ b/cmd/cli/id/id.go
@@ -31,15 +31,11 @@ func NewCmd() *cobra.Command {
 	idCmd := &cobra.Command{
 		Use:   "id",
 		Short: "Show bacalhau node id info",
-		PreRun: func(cmd *cobra.Command, _ []string) {
-			if err := configflags.BindFlags(cmd, idFlags); err != nil {
-				util.Fatal(cmd, err, 1)
-			}
+		PreRunE: func(cmd *cobra.Command, _ []string) error {
+			return configflags.BindFlags(cmd, idFlags)
 		},
-		Run: func(cmd *cobra.Command, _ []string) {
-			if err := id(cmd, outputOpts); err != nil {
-				util.Fatal(cmd, err, 1)
-			}
+		RunE: func(cmd *cobra.Command, _ []string) error {
+			return id(cmd, outputOpts)
 		},
 	}
 
diff --git a/cmd/cli/job/describe.go b/cmd/cli/job/describe.go
index c76088caee..9eebe1e29f 100644
--- a/cmd/cli/job/describe.go
+++ b/cmd/cli/job/describe.go
@@ -54,13 +54,13 @@ func NewDescribeCmd() *cobra.Command {
 		Long:    describeLong,
 		Example: describeExample,
 		Args:    cobra.ExactArgs(1),
-		Run:     o.run,
+		RunE:    o.run,
 	}
 	jobCmd.Flags().AddFlagSet(cliflags.OutputNonTabularFormatFlags(&o.OutputOpts))
 	return jobCmd
 }
 
-func (o *DescribeOptions) run(cmd *cobra.Command, args []string) {
+func (o *DescribeOptions) run(cmd *cobra.Command, args []string) error {
 	ctx := cmd.Context()
 	jobID := args[0]
 	response, err := util.GetAPIClientV2(cmd).Jobs().Get(ctx, &apimodels.GetJobRequest{
@@ -69,14 +69,14 @@ func (o *DescribeOptions) run(cmd *cobra.Command, args []string) {
 	})
 
 	if err != nil {
-		util.Fatal(cmd, fmt.Errorf("could not get job %s: %w", jobID, err), 1)
+		return fmt.Errorf("could not get job %s: %w", jobID, err)
 	}
 
 	if o.OutputOpts.Format != "" {
 		if err = output.OutputOneNonTabular(cmd, o.OutputOpts, response); err != nil {
-			util.Fatal(cmd, fmt.Errorf("failed to write job %s: %w", jobID, err), 1)
+			return fmt.Errorf("failed to write job %s: %w", jobID, err)
 		}
-		return
+		return nil
 	}
 
 	job := response.Job
@@ -89,9 +89,11 @@ func (o *DescribeOptions) run(cmd *cobra.Command, args []string) {
 	o.printHeaderData(cmd, job)
 	o.printExecutionsSummary(cmd, executions)
 	if err = o.printExecutions(cmd, executions); err != nil {
-		util.Fatal(cmd, fmt.Errorf("failed to write job executions %s: %w", jobID, err), 1)
+		return fmt.Errorf("failed to write job executions %s: %w", jobID, err)
 	}
 	o.printOutputs(cmd, executions)
+
+	return nil
 }
 
 func (o *DescribeOptions) printHeaderData(cmd *cobra.Command, job *models.Job) {
diff --git a/cmd/cli/job/executions.go b/cmd/cli/job/executions.go
index 82804eb60e..699b333009 100644
--- a/cmd/cli/job/executions.go
+++ b/cmd/cli/job/executions.go
@@ -59,7 +59,7 @@ func NewExecutionCmd() *cobra.Command {
 		Long:    executionLong,
 		Example: executionExample,
 		Args:    cobra.ExactArgs(1),
-		Run:     o.run,
+		RunE:    o.run,
 	}
 
 	nodeCmd.Flags().AddFlagSet(cliflags.ListFlags(&o.ListOptions))
@@ -120,7 +120,7 @@ var executionColumns = []output.TableColumn[*models.Execution]{
 	executionColumnDesired,
 }
 
-func (o *ExecutionOptions) run(cmd *cobra.Command, args []string) {
+func (o *ExecutionOptions) run(cmd *cobra.Command, args []string) error {
 	ctx := cmd.Context()
 	jobID := args[0]
 	response, err := util.GetAPIClientV2(cmd).Jobs().Executions(ctx, &apimodels.ListJobExecutionsRequest{
@@ -133,10 +133,12 @@ func (o *ExecutionOptions) run(cmd *cobra.Command, args []string) {
 		},
 	})
 	if err != nil {
-		util.Fatal(cmd, err, 1)
+		return err
 	}
 
 	if err = output.Output(cmd, executionColumns, o.OutputOptions, response.Executions); err != nil {
-		util.Fatal(cmd, fmt.Errorf("failed to output: %w", err), 1)
+		return fmt.Errorf("failed to output: %w", err)
 	}
+
+	return nil
 }
diff --git a/cmd/cli/job/history.go b/cmd/cli/job/history.go
index 616d17a875..dbe5cad02a 100644
--- a/cmd/cli/job/history.go
+++ b/cmd/cli/job/history.go
@@ -63,7 +63,7 @@ func NewHistoryCmd() *cobra.Command {
 		Long:    historyLong,
 		Example: historyExample,
 		Args:    cobra.ExactArgs(1),
-		Run:     o.run,
+		RunE:    o.run,
 	}
 
 	nodeCmd.Flags().StringVar(&o.EventType, "event-type", o.EventType,
@@ -123,7 +123,7 @@ var historyColumns = []output.TableColumn[*models.JobHistory]{
 	},
 }
 
-func (o *HistoryOptions) run(cmd *cobra.Command, args []string) {
+func (o *HistoryOptions) run(cmd *cobra.Command, args []string) error {
 	ctx := cmd.Context()
 	jobID := args[0]
 	response, err := util.GetAPIClientV2(cmd).Jobs().History(ctx, &apimodels.ListJobHistoryRequest{
@@ -139,10 +139,12 @@ func (o *HistoryOptions) run(cmd *cobra.Command, args []string) {
 		},
 	})
 	if err != nil {
-		util.Fatal(cmd, err, 1)
+		return err
 	}
 
 	if err = output.Output(cmd, historyColumns, o.OutputOptions, response.History); err != nil {
-		util.Fatal(cmd, fmt.Errorf("failed to output: %w", err), 1)
+		return fmt.Errorf("failed to output: %w", err)
 	}
+
+	return nil
 }
diff --git a/cmd/cli/job/list.go b/cmd/cli/job/list.go
index 58e799b50a..9eef6a12bc 100644
--- a/cmd/cli/job/list.go
+++ b/cmd/cli/job/list.go
@@ -68,7 +68,7 @@ func NewListCmd() *cobra.Command {
 		Long:    listLong,
 		Example: listExample,
 		Args:    cobra.NoArgs,
-		Run:     o.run,
+		RunE:    o.run,
 	}
 
 	listCmd.Flags().StringVar(&o.Labels, "labels", o.Labels,
@@ -113,7 +113,7 @@ var listColumns = []output.TableColumn[*models.Job]{
 	},
 }
 
-func (o *ListOptions) run(cmd *cobra.Command, _ []string) {
+func (o *ListOptions) run(cmd *cobra.Command, _ []string) error {
 	ctx := cmd.Context()
 
 	var err error
@@ -121,7 +121,7 @@ func (o *ListOptions) run(cmd *cobra.Command, _ []string) {
 	if o.Labels != "" {
 		labelRequirements, err = labels.ParseToRequirements(o.Labels)
 		if err != nil {
-			util.Fatal(cmd, fmt.Errorf("could not parse labels: %w", err), 1)
+			return fmt.Errorf("could not parse labels: %w", err)
 		}
 	}
 	response, err := util.GetAPIClientV2(cmd).Jobs().List(ctx, &apimodels.ListJobsRequest{
@@ -134,15 +134,17 @@ func (o *ListOptions) run(cmd *cobra.Command, _ []string) {
 		},
 	})
 	if err != nil {
-		util.Fatal(cmd, fmt.Errorf("failed request: %w", err), 1)
+		return fmt.Errorf("failed request: %w", err)
 	}
 
 	if err = output.Output(cmd, listColumns, o.OutputOptions, response.Jobs); err != nil {
-		util.Fatal(cmd, fmt.Errorf("failed to output: %w", err), 1)
+		return fmt.Errorf("failed to output: %w", err)
 	}
 
 	if response.NextToken != "" {
 		msg := fmt.Sprintf("To fetch more records use `--next-token %s`", response.NextToken)
 		cmd.Printf("\n%s\n", msg)
 	}
+
+	return nil
 }
diff --git a/cmd/cli/job/logs.go b/cmd/cli/job/logs.go
index 2ff0f0d308..bb964ef3cd 100644
--- a/cmd/cli/job/logs.go
+++ b/cmd/cli/job/logs.go
@@ -39,16 +39,14 @@ func NewLogCmd() *cobra.Command {
 		Short:   logsShortDesc,
 		Example: logsExample,
 		Args:    cobra.ExactArgs(1),
-		Run: func(cmd *cobra.Command, cmdArgs []string) {
+		RunE: func(cmd *cobra.Command, cmdArgs []string) error {
 			opts := util.LogOptions{
 				JobID:       cmdArgs[0],
 				ExecutionID: options.ExecutionID,
 				Follow:      options.Follow,
 				Tail:        options.Tail,
 			}
-			if err := util.Logs(cmd, opts); err != nil {
-				util.Fatal(cmd, err, 1)
-			}
+			return util.Logs(cmd, opts)
 		},
 	}
 
diff --git a/cmd/cli/job/run.go b/cmd/cli/job/run.go
index d629c23cc7..040b7b4549 100644
--- a/cmd/cli/job/run.go
+++ b/cmd/cli/job/run.go
@@ -61,7 +61,7 @@ func NewRunCmd() *cobra.Command {
 		Long:    runLong,
 		Example: runExample,
 		Args:    cobra.MinimumNArgs(0),
-		Run:     o.run,
+		RunE:    o.run,
 	}
 
 	runCmd.Flags().AddFlagSet(cliflags.NewRunTimeSettingsFlags(o.RunTimeSettings))
@@ -77,7 +77,7 @@ func NewRunCmd() *cobra.Command {
 	return runCmd
 }
 
-func (o *RunOptions) run(cmd *cobra.Command, args []string) {
+func (o *RunOptions) run(cmd *cobra.Command, args []string) error {
 	ctx := cmd.Context()
 
 	// read the job spec from stdin or file
@@ -86,23 +86,23 @@ func (o *RunOptions) run(cmd *cobra.Command, args []string) {
 	if len(args) == 0 {
 		byteResult, err = util.ReadFromStdinIfAvailable(cmd)
 		if err != nil {
-			util.Fatal(cmd, fmt.Errorf("unknown error reading from file or stdin: %w", err), 1)
+			return fmt.Errorf("unknown error reading from file or stdin: %w", err)
 		}
 	} else {
 		var fileContent *os.File
 		fileContent, err = os.Open(args[0])
 		if err != nil {
-			util.Fatal(cmd, fmt.Errorf("error opening file: %w", err), 1)
+			return fmt.Errorf("error opening file: %w", err)
 		}
 		defer fileContent.Close()
 
 		byteResult, err = io.ReadAll(fileContent)
 		if err != nil {
-			util.Fatal(cmd, fmt.Errorf("error reading file: %w", err), 1)
+			return fmt.Errorf("error reading file: %w", err)
 		}
 	}
 	if len(byteResult) == 0 {
-		util.Fatal(cmd, errors.New(userstrings.JobSpecBad), 1)
+		return errors.New(userstrings.JobSpecBad)
 	}
 
 	if !o.NoTemplate {
@@ -111,13 +111,11 @@ func (o *RunOptions) run(cmd *cobra.Command, args []string) {
 			EnvPattern:   o.TemplateEnvVarsPattern,
 		})
 		if err != nil {
-			util.Fatal(cmd, fmt.Errorf("failed to create template parser: %w", err), 1)
-			return
+			return fmt.Errorf("failed to create template parser: %w", err)
 		}
 		byteResult, err = parser.ParseBytes(byteResult)
 		if err != nil {
-			util.Fatal(cmd, fmt.Errorf("%s: %w", userstrings.JobSpecBad, err), 1)
-			return
+			return fmt.Errorf("%s: %w", userstrings.JobSpecBad, err)
 		}
 	}
 
@@ -126,16 +124,14 @@ func (o *RunOptions) run(cmd *cobra.Command, args []string) {
 	var j *models.Job
 	err = marshaller.YAMLUnmarshalWithMax(byteResult, &j)
 	if err != nil {
-		util.Fatal(cmd, fmt.Errorf("%s: %w", userstrings.JobSpecBad, err), 1)
-		return
+		return fmt.Errorf("%s: %w", userstrings.JobSpecBad, err)
 	}
 
 	// Normalize and validate the job spec
 	j.Normalize()
 	err = j.ValidateSubmission()
 	if err != nil {
-		util.Fatal(cmd, fmt.Errorf("%s: %w", userstrings.JobSpecBad, err), 1)
-		return
+		return fmt.Errorf("%s: %w", userstrings.JobSpecBad, err)
 	}
 
 	if o.RunTimeSettings.DryRun {
@@ -145,9 +141,9 @@ func (o *RunOptions) run(cmd *cobra.Command, args []string) {
 		}
 		outputOps := output.NonTabularOutputOptions{Format: output.YAMLFormat}
 		if err = output.OutputOneNonTabular(cmd, outputOps, j); err != nil {
-			util.Fatal(cmd, fmt.Errorf("failed to write job: %w", err), 1)
+			return fmt.Errorf("failed to write job: %w", err)
 		}
-		return
+		return nil
 	}
 
 	// Submit the job
@@ -156,8 +152,7 @@ func (o *RunOptions) run(cmd *cobra.Command, args []string) {
 		Job: j,
 	})
 	if err != nil {
-		util.Fatal(cmd, fmt.Errorf("failed request: %w", err), 1)
-		return
+		return fmt.Errorf("failed request: %w", err)
 	}
 
 	if o.ShowWarnings && len(resp.Warnings) > 0 {
@@ -165,9 +160,10 @@ func (o *RunOptions) run(cmd *cobra.Command, args []string) {
 	}
 
 	if err := printer.PrintJobExecution(ctx, resp.JobID, cmd, o.RunTimeSettings, client); err != nil {
-		util.Fatal(cmd, fmt.Errorf("failed to print job execution: %w", err), 1)
-		return
+		return fmt.Errorf("failed to print job execution: %w", err)
 	}
+
+	return nil
 }
 
 func (o *RunOptions) printWarnings(cmd *cobra.Command, warnings []string) {
diff --git a/cmd/cli/list/list.go b/cmd/cli/list/list.go
index faaecf0f8d..eed9dd1d2c 100644
--- a/cmd/cli/list/list.go
+++ b/cmd/cli/list/list.go
@@ -75,10 +75,8 @@ func NewCmd() *cobra.Command {
 		Example:  listExample,
 		PreRunE:  hook.RemoteCmdPreRunHooks,
 		PostRunE: hook.RemoteCmdPostRunHooks,
-		Run: func(cmd *cobra.Command, _ []string) {
-			if err := list(cmd, OL); err != nil {
-				util.Fatal(cmd, err, 1)
-			}
+		RunE: func(cmd *cobra.Command, _ []string) error {
+			return list(cmd, OL)
 		},
 	}
 
diff --git a/cmd/cli/node/describe.go b/cmd/cli/node/describe.go
index 6206e3a63a..9ff61903ee 100644
--- a/cmd/cli/node/describe.go
+++ b/cmd/cli/node/describe.go
@@ -29,24 +29,26 @@ func NewDescribeCmd() *cobra.Command {
 		Use:   "describe [id]",
 		Short: "Get the info of a node by id.",
 		Args:  cobra.ExactArgs(1),
-		Run:   o.runDescribe,
+		RunE:  o.runDescribe,
 	}
 	nodeCmd.Flags().AddFlagSet(cliflags.OutputNonTabularFormatFlags(&o.OutputOpts))
 	return nodeCmd
 }
 
 // Run executes node command
-func (o *DescribeOptions) runDescribe(cmd *cobra.Command, args []string) {
+func (o *DescribeOptions) runDescribe(cmd *cobra.Command, args []string) error {
 	ctx := cmd.Context()
 	nodeID := args[0]
 	response, err := util.GetAPIClientV2(cmd).Nodes().Get(ctx, &apimodels.GetNodeRequest{
 		NodeID: nodeID,
 	})
 	if err != nil {
-		util.Fatal(cmd, fmt.Errorf("could not get node %s: %w", nodeID, err), 1)
+		return fmt.Errorf("could not get node %s: %w", nodeID, err)
 	}
 
 	if err = output.OutputOneNonTabular(cmd, o.OutputOpts, response.Node); err != nil {
-		util.Fatal(cmd, fmt.Errorf("failed to write node %s: %w", nodeID, err), 1)
+		return fmt.Errorf("failed to write node %s: %w", nodeID, err)
 	}
+
+	return nil
 }
diff --git a/cmd/cli/node/list.go b/cmd/cli/node/list.go
index 7a1fe44c04..cd6a104c5e 100644
--- a/cmd/cli/node/list.go
+++ b/cmd/cli/node/list.go
@@ -69,7 +69,7 @@ func (o *ListOptions) run(cmd *cobra.Command, _ []string) error {
 	if o.Labels != "" {
 		labelRequirements, err = labels.ParseToRequirements(o.Labels)
 		if err != nil {
-			util.Fatal(cmd, fmt.Errorf("could not parse labels: %w", err), 1)
+			return fmt.Errorf("could not parse labels: %w", err)
 		}
 	}
 
@@ -97,7 +97,7 @@ func (o *ListOptions) run(cmd *cobra.Command, _ []string) error {
 		},
 	})
 	if err != nil {
-		util.Fatal(cmd, fmt.Errorf("failed request: %w", err), 1)
+		return fmt.Errorf("failed request: %w", err)
 	}
 
 	columns := alwaysColumns
@@ -106,7 +106,7 @@ func (o *ListOptions) run(cmd *cobra.Command, _ []string) error {
 	}
 
 	if err = output.Output(cmd, columns, o.OutputOptions, response.Nodes); err != nil {
-		util.Fatal(cmd, fmt.Errorf("failed to output: %w", err), 1)
+		return fmt.Errorf("failed to output: %w", err)
 	}
 
 	return nil
diff --git a/cmd/cli/serve/serve.go b/cmd/cli/serve/serve.go
index 68222b4f1b..6168238cea 100644
--- a/cmd/cli/serve/serve.go
+++ b/cmd/cli/serve/serve.go
@@ -126,7 +126,7 @@ func NewCmd() *cobra.Command {
 		Short:   "Start the bacalhau compute node",
 		Long:    serveLong,
 		Example: serveExample,
-		PreRun: func(cmd *cobra.Command, args []string) {
+		PreRunE: func(cmd *cobra.Command, args []string) error {
 			/*
 				NB(forrest):
 				(I learned a lot more about viper and cobra than was intended...)
@@ -150,9 +150,7 @@ func NewCmd() *cobra.Command {
 				return the value of the last flag bound to it. This is why it's important to manage
 				flag binding thoughtfully, ensuring each command's context is respected.
 			*/
-			if err := configflags.BindFlags(cmd, serveFlags); err != nil {
-				util.Fatal(cmd, err, 1)
-			}
+			return configflags.BindFlags(cmd, serveFlags)
 		},
 		RunE: func(cmd *cobra.Command, _ []string) error {
 			return serve(cmd)
diff --git a/cmd/cli/validate/validate.go b/cmd/cli/validate/validate.go
index a52e4ddb1c..e6eb5c8047 100644
--- a/cmd/cli/validate/validate.go
+++ b/cmd/cli/validate/validate.go
@@ -10,7 +10,6 @@ import (
 	"github.com/invopop/jsonschema"
 	"github.com/spf13/cobra"
 
-	"github.com/bacalhau-project/bacalhau/cmd/util"
 	"github.com/bacalhau-project/bacalhau/pkg/model"
 	"github.com/bacalhau-project/bacalhau/pkg/util/templates"
 
@@ -67,10 +66,8 @@ func NewCmd() *cobra.Command {
 		Long:    validateLong,
 		Example: validateExample,
 		Args:    cobra.MinimumNArgs(0),
-		Run: func(cmd *cobra.Command, cmdArgs []string) { //nolint:unparam // incorrect that cmd is unused.
-			if err := validate(cmd, cmdArgs, OV); err != nil {
-				util.Fatal(cmd, err, 1)
-			}
+		RunE: func(cmd *cobra.Command, cmdArgs []string) error {
+			return validate(cmd, cmdArgs, OV)
 		},
 	}
 
diff --git a/cmd/cli/version/version.go b/cmd/cli/version/version.go
index f3010d03db..d26f8b8d67 100644
--- a/cmd/cli/version/version.go
+++ b/cmd/cli/version/version.go
@@ -50,10 +50,8 @@ func NewCmd() *cobra.Command {
 		Short:  "Get the client and server version.",
 		Args:   cobra.NoArgs,
 		PreRun: hook.ApplyPorcelainLogLevel,
-		Run: func(cmd *cobra.Command, _ []string) {
-			if err := runVersion(cmd, oV); err != nil {
-				util.Fatal(cmd, err, 1)
-			}
+		RunE: func(cmd *cobra.Command, _ []string) error {
+			return runVersion(cmd, oV)
 		},
 	}
 	versionCmd.Flags().BoolVar(&oV.ClientOnly, "client", oV.ClientOnly, "If true, shows client version only (no server required).")
diff --git a/cmd/cli/wasm/wasm_run.go b/cmd/cli/wasm/wasm_run.go
index d45049801f..cae8df28ae 100644
--- a/cmd/cli/wasm/wasm_run.go
+++ b/cmd/cli/wasm/wasm_run.go
@@ -102,10 +102,8 @@ func newRunCmd() *cobra.Command {
 		Args:     cobra.MinimumNArgs(1),
 		PreRunE:  hook.Chain(hook.ClientPreRunHooks, configflags.PreRun(wasmRunFlags)),
 		PostRunE: hook.ClientPostRunHooks,
-		Run: func(cmd *cobra.Command, args []string) {
-			if err := runWasm(cmd, args, opts); err != nil {
-				util.Fatal(cmd, err, 1)
-			}
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runWasm(cmd, args, opts)
 		},
 	}
 
diff --git a/go.work.sum b/go.work.sum
index eee5b88d45..32aae5d08e 100644
--- a/go.work.sum
+++ b/go.work.sum
@@ -1218,6 +1218,7 @@ github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b h1:ACGZRIr7HsgBKHsueQ1
 github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
 github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
 github.com/cockroachdb/cockroach-go/v2 v2.1.1 h1:3XzfSMuUT0wBe1a3o5C0eOTcArhmmFAg2Jzh/7hhKqo=
 github.com/cockroachdb/cockroach-go/v2 v2.1.1/go.mod h1:7NtUnP6eK+l6k483WSYNrq3Kb23bWV10IRV1TyeSpwM=


Test patch:
diff --git a/cmd/cli/create/create_test.go b/cmd/cli/create/create_test.go
index 8ce6684fd5..ca0087446b 100644
--- a/cmd/cli/create/create_test.go
+++ b/cmd/cli/create/create_test.go
@@ -5,11 +5,9 @@ package create_test
 import (
 	"context"
 	"os"
-	"strings"
 	"sync"
 	"testing"
 
-	"github.com/bacalhau-project/bacalhau/pkg/lib/marshaller"
 	"github.com/spf13/cobra"
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/suite"
@@ -139,14 +137,5 @@ func (s *CreateSuite) TestCreateDontPanicOnEmptyFile() {
 
 	commandReturnValue := <-commandChan
 
-	errorOutputMap := make(map[string]interface{})
-	for _, o := range strings.Split(commandReturnValue.out, "\n") {
-		err := marshaller.YAMLUnmarshalWithMax([]byte(o), &errorOutputMap)
-		if err != nil {
-			continue
-		}
-	}
-
-	require.Contains(s.T(), errorOutputMap["Message"], "the job provided is invalid", "Output message should error properly.")
-	require.Equal(s.T(), int(errorOutputMap["Code"].(float64)), 1, "Expected no error when no input is provided")
+	require.Contains(s.T(), commandReturnValue.out, "the job provided is invalid", "Output message should error properly.")
 }
diff --git a/cmd/cli/describe/describe_test.go b/cmd/cli/describe/describe_test.go
index dda4225585..2e133e88fa 100644
--- a/cmd/cli/describe/describe_test.go
+++ b/cmd/cli/describe/describe_test.go
@@ -13,7 +13,6 @@ import (
 	"github.com/stretchr/testify/suite"
 
 	cmdtesting "github.com/bacalhau-project/bacalhau/cmd/testing"
-	"github.com/bacalhau-project/bacalhau/cmd/util"
 	"github.com/bacalhau-project/bacalhau/pkg/bacerrors"
 	legacy_job "github.com/bacalhau-project/bacalhau/pkg/legacyjob"
 	"github.com/bacalhau-project/bacalhau/pkg/model"
@@ -196,7 +195,6 @@ func (s *DescribeSuite) TestDescribeJobEdgeCases() {
 		{numOfJobs: 1}, // just enough that describe could get screwed up
 	}
 
-	util.Fatal = util.FakeFatalErrorHandler
 	for _, tc := range tests {
 		for _, n := range numOfJobsTests {
 			func() {
@@ -247,10 +245,8 @@ func (s *DescribeSuite) TestDescribeJobEdgeCases() {
 						returnedJobEngineSpec,
 						fmt.Sprintf("Submitted job entrypoints not the same as the description. Edgecase: %s", tc.describeIDEdgecase))
 				} else {
-					c := &model.TestFatalErrorHandlerContents{}
-					s.Require().NoError(model.JSONUnmarshalWithMax([]byte(out), &c))
 					e := bacerrors.NewJobNotFound(tc.describeIDEdgecase)
-					s.Require().Contains(c.Message, e.GetMessage(), "Job not found error string not found.", err)
+					s.Require().Contains(string(out), e.GetMessage(), "Job not found error string not found.", err)
 				}
 
 			}()
diff --git a/cmd/cli/docker/docker_run_test.go b/cmd/cli/docker/docker_run_test.go
index 54f66fbca7..14f14a3d64 100644
--- a/cmd/cli/docker/docker_run_test.go
+++ b/cmd/cli/docker/docker_run_test.go
@@ -21,7 +21,6 @@ import (
 	"github.com/bacalhau-project/bacalhau/pkg/models"
 
 	cmdtesting "github.com/bacalhau-project/bacalhau/cmd/testing"
-	"github.com/bacalhau-project/bacalhau/cmd/util"
 	"github.com/bacalhau-project/bacalhau/pkg/devstack"
 	"github.com/bacalhau-project/bacalhau/pkg/docker"
 	"github.com/bacalhau-project/bacalhau/pkg/ipfs"
@@ -45,7 +44,6 @@ type DockerRunSuite struct {
 // In order for 'go test' to run this suite, we need to create
 // a normal test function and pass our suite to suite.Run
 func TestDockerRunSuite(t *testing.T) {
-	util.Fatal = util.FakeFatalErrorHandler
 	suite.Run(t, new(DockerRunSuite))
 }
 
@@ -359,11 +357,8 @@ func (s *DockerRunSuite) TestRun_SubmitOutputs() {
 				_, out, err := s.ExecuteTestCobraCommand(flagsArray...)
 
 				if tcids.err != "" {
-					firstFatalError, err := testutils.FirstFatalError(s.T(), out)
-
-					s.Require().NoErrorf(err, "Error unmarshaling errors. Run - Number of Jobs: %s. Job number: %s", tc.numberOfJobs, i)
-					s.Require().Greaterf(firstFatalError.Code, 0, "Expected an error, but none provided. %+v", tcids)
-					s.Require().Contains(firstFatalError.Message, "invalid output volume", "Missed detection of invalid output volume.")
+					s.Require().Error(err)
+					s.Require().Contains(string(out), "invalid output volume", "Missed detection of invalid output volume.")
 					return // Go to next in loop
 				}
 				s.Require().NoError(err, "Error submitting job. Run - Number of Jobs: %d. Job number: %d", tc.numberOfJobs, i)
@@ -579,10 +574,7 @@ func (s *DockerRunSuite) TestRun_SubmitWorkdir() {
 			_, out, err := s.ExecuteTestCobraCommand(flagsArray...)
 
 			if tc.errorCode != 0 {
-				fatalError, err := testutils.FirstFatalError(s.T(), out)
-				s.Require().NoError(err, "Error getting first fatal error")
-
-				s.Require().NotNil(fatalError, "Expected fatal error, but none found")
+				s.Require().NotNil(err, "Expected fatal error, but none found")
 			} else {
 				s.Require().NoError(err, "Error submitting job.")
 
diff --git a/cmd/cli/validate/validate_test.go b/cmd/cli/validate/validate_test.go
index a533204686..7e12d600c4 100644
--- a/cmd/cli/validate/validate_test.go
+++ b/cmd/cli/validate/validate_test.go
@@ -10,8 +10,6 @@ import (
 	"github.com/stretchr/testify/suite"
 
 	cmdtesting "github.com/bacalhau-project/bacalhau/cmd/testing"
-	"github.com/bacalhau-project/bacalhau/cmd/util"
-	testutils "github.com/bacalhau-project/bacalhau/pkg/test/utils"
 	"github.com/bacalhau-project/bacalhau/testdata"
 )
 
@@ -33,8 +31,6 @@ func (s *ValidateSuite) TestValidate() {
 	}
 	for name, test := range tests {
 		s.Run(name, func() {
-			util.Fatal = util.FakeFatalErrorHandler
-
 			_, out, err := s.ExecuteTestCobraCommand("validate",
 				test.testFile.AsTempFile(s.T(), fmt.Sprintf("%s.*.yaml", name)),
 			)
@@ -44,10 +40,10 @@ func (s *ValidateSuite) TestValidate() {
 				require.Contains(s.T(), out, "The Job is valid", fmt.Sprintf("%s: Jobspec Invalid", name))
 			} else {
 				require.Error(s.T(), err)
-				fatalError, err := testutils.FirstFatalError(s.T(), out)
-				require.NoError(s.T(), err)
-				require.Contains(s.T(), fatalError.Message, "The Job is not valid.", fmt.Sprintf("%s: Jobspec Invalid returning valid", name))
-				require.Contains(s.T(), fatalError.Message, "APIVersion is required", fmt.Sprintf("%s: Jobspec Invalid returning valid", name))
+
+				fatalError := string(out)
+				require.Contains(s.T(), fatalError, "The Job is not valid.", fmt.Sprintf("%s: Jobspec Invalid returning valid", name))
+				require.Contains(s.T(), fatalError, "APIVersion is required", fmt.Sprintf("%s: Jobspec Invalid returning valid", name))
 			}
 		})
 
diff --git a/cmd/cli/wasm/wasm_run_test.go b/cmd/cli/wasm/wasm_run_test.go
index df80802b10..484fbdefd5 100644
--- a/cmd/cli/wasm/wasm_run_test.go
+++ b/cmd/cli/wasm/wasm_run_test.go
@@ -10,7 +10,6 @@ import (
 	"github.com/stretchr/testify/suite"
 
 	cmdtesting "github.com/bacalhau-project/bacalhau/cmd/testing"
-	"github.com/bacalhau-project/bacalhau/cmd/util"
 	"github.com/bacalhau-project/bacalhau/pkg/model"
 	testutils "github.com/bacalhau-project/bacalhau/pkg/test/utils"
 )
@@ -20,7 +19,6 @@ type WasmRunSuite struct {
 }
 
 func TestWasmRunSuite(t *testing.T) {
-	util.Fatal = util.FakeFatalErrorHandler
 	suite.Run(t, new(WasmRunSuite))
 }
 
diff --git a/cmd/testing/base.go b/cmd/testing/base.go
index 20b655600d..823d366c08 100644
--- a/cmd/testing/base.go
+++ b/cmd/testing/base.go
@@ -37,7 +37,6 @@ type BaseSuite struct {
 // before each test
 func (s *BaseSuite) SetupTest() {
 	logger.ConfigureTestLogging(s.T())
-	util.Fatal = util.FakeFatalErrorHandler
 
 	computeConfig, err := node.NewComputeConfigWith(node.ComputeConfigParams{
 		JobSelectionPolicy: node.JobSelectionPolicy{
@@ -70,7 +69,6 @@ func (s *BaseSuite) SetupTest() {
 
 // After each test
 func (s *BaseSuite) TearDownTest() {
-	util.Fatal = util.FakeFatalErrorHandler
 	if s.Node != nil {
 		s.Node.CleanupManager.Cleanup(context.Background())
 	}
diff --git a/cmd/testing/basetls.go b/cmd/testing/basetls.go
index 04fda1d087..9d5867423b 100644
--- a/cmd/testing/basetls.go
+++ b/cmd/testing/basetls.go
@@ -6,7 +6,6 @@ import (
 	"path/filepath"
 	"time"
 
-	"github.com/bacalhau-project/bacalhau/cmd/util"
 	"github.com/bacalhau-project/bacalhau/pkg/bidstrategy/semantic"
 	"github.com/bacalhau-project/bacalhau/pkg/devstack"
 	noop_executor "github.com/bacalhau-project/bacalhau/pkg/executor/noop"
@@ -26,7 +25,6 @@ type BaseTLSSuite struct {
 // before each test
 func (s *BaseTLSSuite) SetupTest() {
 	logger.ConfigureTestLogging(s.T())
-	util.Fatal = util.FakeFatalErrorHandler
 
 	computeConfig, err := node.NewComputeConfigWith(node.ComputeConfigParams{
 		JobSelectionPolicy: node.JobSelectionPolicy{
@@ -71,7 +69,6 @@ func (s *BaseTLSSuite) SetupTest() {
 
 // After each test
 func (s *BaseTLSSuite) TearDownTest() {
-	util.Fatal = util.FakeFatalErrorHandler
 	if s.Node != nil {
 		s.Node.CleanupManager.Cleanup(context.Background())
 	}