Skip to content

rtk test does not normalise an even, non-zero negation count — returns the child's status where a shell returns 0/1 #4289

Open

Description

@KuSh

rtk test applies the ! negation that the interposed sh -c string used to get from the shell. An odd count inverts the child's status, which is right. An even, non-zero count returns the child's status verbatim instead of normalising it to 0/1, so the answer can be a code no shell would give.

Found while reviewing #4125, on its head. Not a regression — see below — so it did not hold that PR.

Symptom

$ rtk test ! ( ! sh -c 'exit 2' )
exit 2
$ bash -c '! ( ! sh -c "exit 2" )'
exit 1

bash normalises: the result of a negation is a truth value, 0 or 1, never the operand's code. rtk returns 2, which reads as "the command failed with status 2" when what happened is "the doubly-negated condition is false".

Mechanism

src/main.rs, the Commands::Test arm:

if negations % 2 == 1 {
    i32::from(code == 0)
} else {
    code
}

The else is correct for negations == 0 and wrong for every even count above it. split_leading_negations peels !s and a wrapping ( … ) in a loop, so negations >= 2 arrives here from ! ! and, since #4125, from ! ( ! … ) as well.

Shape that closes it:

if negations % 2 == 1 {
    i32::from(code == 0)
} else if negations > 0 {
    i32::from(code != 0)
} else {
    code
}

Why this is not a regression

The merge-base answered 0 for the same input — a reported pass for a command that failed, because the joined sh -c string took a different route through the shell. So the current code moved the answer into the right direction (non-zero for a false condition) and stopped short of the right value. Worth fixing on its own terms rather than as a regression.

Test

tests/run_shell_test.rs::grouped_and_negated_commands_keep_their_exit_codes covers eight spellings but cannot see this: its ! ! ( true ) case uses true, whose status is already 0, so normalising and not normalising agree. A case whose child exits non-zero under an even negation count — ! ! sh -c 'exit 2', ! ( ! sh -c 'exit 2' ) — is what pins it, asserted against bash.

Activity

  1. rtk-wshm-sync-bot commented on Sep 26, 2026

    @rtk-wshm-sync-bot

    wshm · Automated triage by AI

    🔍 Automated Triage

    🐛 Category bug
    🟡 Priority medium
    🎯 Confidence 94%

    Summary

    rtk test fails to normalize exit code to 0/1 when negation count is even and non-zero, returning raw child status instead of a truth value like bash does.

    This looks like a simple fix that could be auto-resolved. Repo maintainers can use /wshm fix to attempt it.

    📁 Relevant files
    • src/main.rs
    • tests/run_shell_test.rs

    Suggested Actions

    1. Apply the fix shape given: add else if negations > 0 { i32::from(code != 0) } branch in src/main.rs Commands::Test arm
    2. Add test case ! ! sh -c 'exit 2' / ! ( ! sh -c 'exit 2' ) to tests/run_shell_test.rs, asserted against bash
    3. Note interaction with A case (or subshell / brace group) anywhere on a line disables rewriting of every pipeline on that line #4283 (case/subshell disabling rewriting) since both touch negation/grouping parsing

    Triaged automatically by wshm · This is an automated analysis, not a human review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions