Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ Begin agent-assisted development era.
- README: improved Contributing section for both agents and humans
- LICENSE: updated copyright to include Equinix Metal and 2025 A. Tobey

### Fixed

- `exec` exits 128 + signal when the child is killed by a signal, not 255 (Unix)
- `exec` no longer panics on Ctrl-C during the span export

## [0.5.0] - 2025-04-21

Fork otel-cli to @tobert's personal GitHub.
Expand Down
33 changes: 23 additions & 10 deletions data_for_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -583,14 +583,27 @@ var suites = []FixtureSuite{
IsLongTest: true,
},
Expect: Results{
Config: otelcli.DefaultConfig(),
Config: otelcli.DefaultConfig(),
ExitCode: 42,
},
CheckFuncs: []CheckFunc{
func(t *testing.T, f Fixture, r Results) {
if r.ExitCode != 42 {
t.Errorf("expected exit code 42 from child process, got %d", r.ExitCode)
}
},
{
Name: "exec ctrl-c during export does not panic",
Config: FixtureConfig{
CliArgs: []string{"exec",
"--endpoint", "{{endpoint}}",
"--timeout", "1s",
"--", "/bin/true",
},
StopServerBeforeExec: true,
KillAfter: time.Millisecond * 200, // child is done, export is retrying
KillSignal: syscall.SIGINT,
TestTimeoutMs: 3000,
IsLongTest: true,
},
Expect: Results{
Config: otelcli.DefaultConfig(),
CommandFailed: true, // ended by the signal, not a panic (exit 2)
},
},
},
Expand Down Expand Up @@ -1155,14 +1168,14 @@ var suites = []FixtureSuite{
CliOutputRe: regexp.MustCompile(`^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} `),
CliOutput: "invalid protocol setting \"xxx\"\n",
Config: otelcli.DefaultConfig().WithEndpoint("{{endpoint}}"),
ExitCode: 1, // --fail
Diagnostics: otelcli.Diagnostics{
IsRecording: false,
NumArgs: 7,
DetectedLocalhost: true,
ParsedTimeoutMs: 1000,
Endpoint: "*",
EndpointSource: "*",
ExecExitCode: 1,
},
SpanCount: 0,
},
Expand Down Expand Up @@ -1403,7 +1416,7 @@ var suites = []FixtureSuite{
Expect: Results{
SpanCount: 1,
Config: otelcli.DefaultConfig().WithEndpoint("{{endpoint}}"),
ExitCode: 2,
ExitCode: 130, // 128 + SIGINT, as shells report it
SpanData: map[string]string{
"status_code": "2",
"status_description": "exec command failed: signal: interrupt",
Expand All @@ -1423,7 +1436,7 @@ var suites = []FixtureSuite{
Expect: Results{
SpanCount: 1,
Config: otelcli.DefaultConfig().WithEndpoint("{{endpoint}}"),
ExitCode: 2,
ExitCode: 137, // 128 + SIGKILL
SpanData: map[string]string{
"status_code": "2",
"status_description": "exec command failed: signal: killed",
Expand All @@ -1444,7 +1457,7 @@ var suites = []FixtureSuite{
Expect: Results{
SpanCount: 1,
Config: otelcli.DefaultConfig().WithEndpoint("{{endpoint}}"),
ExitCode: 0,
ExitCode: 137, // --command-timeout kills sleep at 100ms
},
},
},
Expand Down
6 changes: 6 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ func checkProcess(t *testing.T, fixture Fixture, results Results) bool {
t.Errorf("[%s] command failed is %t but expected %t", fixture.Name, results.CommandFailed, fixture.Expect.CommandFailed)
return false
}
// a killed process has no exit code of its own; TimedOut alone is not enough
// to skip, because the server timer sets it even when otel-cli exits normally
if !results.CommandFailed && results.ExitCode != fixture.Expect.ExitCode {
t.Errorf("[%s] exit code is %d but expected %d", fixture.Name, results.ExitCode, fixture.Expect.ExitCode)
return false
}
return true
}

Expand Down
8 changes: 8 additions & 0 deletions otelcli/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os/signal"
"os/user"
"strings"
"syscall"
"time"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -156,11 +157,18 @@ func doExec(cmd *cobra.Command, args []string) {
// capture the child's exit code before OTLP export so SoftFail can use it (#360)
if child.ProcessState != nil {
Diag.ExecExitCode = child.ProcessState.ExitCode()
// a child killed by a signal reports -1, which os.Exit turns into 255;
// exit 128 + signal instead, as shells do
if status, ok := child.ProcessState.Sys().(syscall.WaitStatus); ok && status.Signaled() {
Diag.ExecExitCode = 128 + int(status.Signal())
}
} else {
Diag.ExecExitCode = 127
}

cancelCtxDeadline()
// stop delivery before close, or a ctrl-c during export panics on a closed channel
signal.Stop(signals)
close(signals)
<-signalsDone

Expand Down
Loading