diff --git a/data_for_test.go b/data_for_test.go index 6658eac..11bf2bc 100644 --- a/data_for_test.go +++ b/data_for_test.go @@ -1434,4 +1434,38 @@ var suites = []FixtureSuite{ }, }, }, + // exec with non-existent command should fail gracefully + { + { + Name: "exec non-existent command should not panic", + Config: FixtureConfig{ + CliArgs: []string{"exec", "--endpoint", "{{endpoint}}", "command-that-does-not-exist-anywhere"}, + }, + Expect: Results{ + SpanCount: 1, + ExitCode: 127, // exit 127 like shell does for "command not found" + Config: otelcli.DefaultConfig().WithEndpoint("{{endpoint}}"), + }, + CheckFuncs: []CheckFunc{ + func(t *testing.T, f Fixture, r Results) { + // should send a span with error status + if r.Span == nil { + t.Errorf("expected a span to be sent even when command fails to start") + return + } + if r.Span.Status == nil { + t.Errorf("expected span status to be set") + return + } + if r.Span.Status.Code != 2 { // STATUS_CODE_ERROR + t.Errorf("expected span status code ERROR (2), got %d", r.Span.Status.Code) + } + // error message should mention the command failure + if !strings.Contains(r.Span.Status.Message, "exec command failed") { + t.Errorf("expected error message to contain 'exec command failed', got: %q", r.Span.Status.Message) + } + }, + }, + }, + }, } diff --git a/otelcli/exec.go b/otelcli/exec.go index 7a6e24b..0a9df63 100644 --- a/otelcli/exec.go +++ b/otelcli/exec.go @@ -146,8 +146,11 @@ func doExec(cmd *cobra.Command, args []string) { // append process attributes span.Attributes = append(span.Attributes, processAttrs...) - pidAttrs := processPidAttrs(config, int64(child.Process.Pid), int64(os.Getpid())) - span.Attributes = append(span.Attributes, pidAttrs...) + // child.Process is nil if the command failed to start (e.g., command not found) + if child.Process != nil { + pidAttrs := processPidAttrs(config, int64(child.Process.Pid), int64(os.Getpid())) + span.Attributes = append(span.Attributes, pidAttrs...) + } cancelCtxDeadline() close(signals) @@ -169,7 +172,14 @@ func doExec(cmd *cobra.Command, args []string) { } // set the global exit code so main() can grab it and os.Exit() properly - Diag.ExecExitCode = child.ProcessState.ExitCode() + // ProcessState is nil if the command failed to start + if child.ProcessState != nil { + Diag.ExecExitCode = child.ProcessState.ExitCode() + } else { + // command failed to start (e.g., command not found) + // use exit code 127 to match shell behavior for "command not found" + Diag.ExecExitCode = 127 + } config.PropagateTraceparent(span, os.Stdout) }