Skip to content

Commit 0b5188f

Browse files
authored
fix: handle nil Process when exec command doesn't exist (#16)
Why: otel-cli exec crashes with nil pointer when command not found (issue #8) Approach: Check for nil Process and ProcessState, return exit 127 to match shell Learned: Shell returns 127 for "command not found", principle of least surprise Next: Create PR and close issue Changes: - Check child.Process != nil before accessing Pid - Check child.ProcessState != nil before getting ExitCode - Return exit code 127 when command fails to start (matches shell behavior) - Sends span with error status even when command doesn't exist Tests: - Added test for exec with non-existent command - Verifies exit code 127 (same as shell) - Verifies span is sent with error status - Verifies no panic/crash Fixes #8 🤖 Claude <[email protected]>
1 parent fc73ce7 commit 0b5188f

File tree

2 files changed

+47
-3
lines changed

2 files changed

+47
-3
lines changed

data_for_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,4 +1434,38 @@ var suites = []FixtureSuite{
14341434
},
14351435
},
14361436
},
1437+
// exec with non-existent command should fail gracefully
1438+
{
1439+
{
1440+
Name: "exec non-existent command should not panic",
1441+
Config: FixtureConfig{
1442+
CliArgs: []string{"exec", "--endpoint", "{{endpoint}}", "command-that-does-not-exist-anywhere"},
1443+
},
1444+
Expect: Results{
1445+
SpanCount: 1,
1446+
ExitCode: 127, // exit 127 like shell does for "command not found"
1447+
Config: otelcli.DefaultConfig().WithEndpoint("{{endpoint}}"),
1448+
},
1449+
CheckFuncs: []CheckFunc{
1450+
func(t *testing.T, f Fixture, r Results) {
1451+
// should send a span with error status
1452+
if r.Span == nil {
1453+
t.Errorf("expected a span to be sent even when command fails to start")
1454+
return
1455+
}
1456+
if r.Span.Status == nil {
1457+
t.Errorf("expected span status to be set")
1458+
return
1459+
}
1460+
if r.Span.Status.Code != 2 { // STATUS_CODE_ERROR
1461+
t.Errorf("expected span status code ERROR (2), got %d", r.Span.Status.Code)
1462+
}
1463+
// error message should mention the command failure
1464+
if !strings.Contains(r.Span.Status.Message, "exec command failed") {
1465+
t.Errorf("expected error message to contain 'exec command failed', got: %q", r.Span.Status.Message)
1466+
}
1467+
},
1468+
},
1469+
},
1470+
},
14371471
}

otelcli/exec.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,11 @@ func doExec(cmd *cobra.Command, args []string) {
146146

147147
// append process attributes
148148
span.Attributes = append(span.Attributes, processAttrs...)
149-
pidAttrs := processPidAttrs(config, int64(child.Process.Pid), int64(os.Getpid()))
150-
span.Attributes = append(span.Attributes, pidAttrs...)
149+
// child.Process is nil if the command failed to start (e.g., command not found)
150+
if child.Process != nil {
151+
pidAttrs := processPidAttrs(config, int64(child.Process.Pid), int64(os.Getpid()))
152+
span.Attributes = append(span.Attributes, pidAttrs...)
153+
}
151154

152155
cancelCtxDeadline()
153156
close(signals)
@@ -169,7 +172,14 @@ func doExec(cmd *cobra.Command, args []string) {
169172
}
170173

171174
// set the global exit code so main() can grab it and os.Exit() properly
172-
Diag.ExecExitCode = child.ProcessState.ExitCode()
175+
// ProcessState is nil if the command failed to start
176+
if child.ProcessState != nil {
177+
Diag.ExecExitCode = child.ProcessState.ExitCode()
178+
} else {
179+
// command failed to start (e.g., command not found)
180+
// use exit code 127 to match shell behavior for "command not found"
181+
Diag.ExecExitCode = 127
182+
}
173183

174184
config.PropagateTraceparent(span, os.Stdout)
175185
}

0 commit comments

Comments
 (0)