Skip to content

fix(stream): report a signal-terminated child instead of exiting 128 + signal silently - #3709

Open
Niko-Wu wants to merge 1 commit into
rtk-ai:developfrom
Niko-Wu:fix/stream-surface-signal-termination
Open

fix(stream): report a signal-terminated child instead of exiting 128 + signal silently#3709
Niko-Wu wants to merge 1 commit into
rtk-ai:developfrom
Niko-Wu:fix/stream-surface-signal-termination

Conversation

@Niko-Wu

@Niko-Wu Niko-Wu commented Aug 25, 2026

Copy link
Copy Markdown

Fixes #3708

Problem

A child killed by a signal has no exit code of its own, so 128 + signal is all that reaches the caller — and that number alone is indistinguishable from a tool which genuinely exited with it. On the streaming path nothing else is emitted, so rtk git push with a child killed by SIGKILL exits 137 with empty stdout and empty stderr. The proxy path already reports the kill, so the two paths disagreed.

Reproduction from the issue (fake git that kills itself, no repo or network needed):

printf '#!/bin/sh\nkill -9 $$\n' > /tmp/fakebin/git && chmod +x /tmp/fakebin/git
PATH=/tmp/fakebin:$PATH rtk git push   # before: exit 137, no output at all
                                       # after:  exit 137, [rtk] git: process terminated by signal 9

Fix

stream::status_to_exit_code now delegates to the existing utils::exit_code_from_status, which already both maps the code and reports the signal. That removes a near-duplicate of the mapping logic and guarantees one wording for every path that spawns a child, per the Extensibility rule in CONTRIBUTING ("use components already in place to avoid duplication").

run_streaming computes the label once and passes it to both call sites, so the message names the tool: [rtk] git: process terminated by signal 9.

Exit codes are unchanged — the only new behaviour is one stderr line on the signal path, which cannot happen on a normal exit.

Trade-offs

  • label parameter. status_to_exit_code gains label: &str. The smaller alternative was printing without a label (no signature change), but every other rtk diagnostic names the command, and the name is what makes the line actionable when several tools are involved. The blast radius is small and crate-internal: 2 call sites plus 3 test assertions.
  • Basename, not the resolved path. The label is the program's basename, so a command resolved through PATH reports as git rather than the absolute path it happened to be found at — shorter, stable across machines, and consistent with how the other diagnostics read.
  • Delegation over a local eprintln!. Copying the message into stream.rs would have been a one-line diff but would leave two copies of the same wording free to drift apart.

Tests

  • tests/signal_termination_test.rs (new, #![cfg(unix)]): puts a fake git that SIGKILLs itself on PATH, runs rtk git push, and asserts both the 137 exit code and a stderr diagnostic that names the command. This covers the streaming path end to end through the real binary.
  • test_command_label_is_program_basename (new unit test): the label is the basename for an absolute path and for a bare name.
  • test_exit_code_signal_kill (existing, still asserting 137) and the two neighbouring exit-code tests are kept, updated only for the new argument.

Gates run locally: cargo fmt --all -- --check, cargo clippy --all-targets, cargo test --all — all pass.

…+ signal silently

A child killed by a signal has no exit code of its own, so `128 + signal` is
all that reaches the caller — a number indistinguishable from a tool that
genuinely exited with it. The streaming path emitted nothing else, so a child
killed by SIGKILL surfaced as exit 137 with empty stdout and empty stderr,
while the proxy path already reported the same situation on stderr.

status_to_exit_code now delegates to utils::exit_code_from_status, which
already maps the code and reports the signal, so every path that spawns a
child shares one implementation and one wording. run_streaming passes the
program basename as the label, so the line reads
`[rtk] git: process terminated by signal 9`.

Exit codes are unchanged; the only new output is one stderr line on the
signal path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Aug 25, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@Niko-Wu
Niko-Wu marked this pull request as draft August 25, 2026 08:06
@Niko-Wu
Niko-Wu marked this pull request as ready for review August 25, 2026 08:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rtk git push exits 128 + signal with no output when the child is killed by a signal (streaming path has no diagnostic)

2 participants