Skip to content

--jobs with a non-integer value hangs on Bash 3.x and is silently ignored on Bash 4.3+ #873

Closed

Description

@Chemaclass

🤔 Background

src/main.sh resolves --jobs auto to an integer and carries this comment:

# "auto" caps at the detected core count; wait_for_job_slot needs an
# integer, so resolve it here rather than leaking the string downstream.

The else branch then assigns $2 verbatim, so the stated invariant is never enforced. Nothing else validates it, and BASHUNIT_PARALLEL_JOBS from the environment bypasses this code entirely.

Observed

bashunit::runner::wait_for_job_slot compares the running-job count against max_jobs with [ ... ], which errors (status 2) on a non-integer operand. The two execution paths then diverge:

Bash 3.x (polling fallback, src/runner.sh:376) — the break guard never succeeds, so the loop never exits. The run hangs, emitting the same error forever:

$ ./bashunit --jobs abc tests/     # /bin/bash 3.2.57
src/runner.sh: line 376: [: abc: integer expression expected
src/runner.sh: line 376: [: abc: integer expression expected
...                                # killed at 15s, no progress

Bash 4.3+ (wait -n path, src/runner.sh:363) — the while guard fails, the loop body never runs, and the concurrency limit is silently dropped:

$ /opt/homebrew/bin/bash ./bashunit --jobs abc tests/   # bash 5.3.15
 All tests passed
$ echo $?
0

So the same command hangs on the bash macOS ships and silently ignores the limit on a modern one.

Related

Same class as #871 (unvalidated CLI input). --output <value> is also accepted with any string and silently ignored:

$ ./bashunit --output nonsense tests/
 All tests passed     # exit 0, default format

Suggested fix

Validate at the boundary the same way bashunit::main::set_shard_or_exit already does — reject a --jobs value that is not a non-negative integer with a clear error and non-zero exit, and apply the same check when BASHUNIT_PARALLEL_JOBS comes from the environment. Worth making wait_for_job_slot fail safe rather than loop forever regardless.

Activity

  1. self-assigned this
    on Jul 25, 2026
  2. Chemaclass commented on Jul 25, 2026

    @Chemaclass
    MemberAuthor

    Swept every value-taking flag with a non-integer and a negative value. None of the numeric flags validate their argument — all exit 0:

    Flag Result
    --jobs abc hangs on Bash 3.x, silently ignored on 4.3+ (original report)
    --jobs -5 silently ignored, exit 0
    --retry abc silently ignored, exit 0
    --retry -3 silently ignored, exit 0
    --test-timeout abc silently ignored, exit 0
    --seed abc silently ignored, exit 0
    --output nonsense silently ignored, exit 0 (falls back to the default format)
    --coverage-min abc leaks a raw shell error, still exit 0

    --coverage-min is the one that surfaces anything at all, and only as an unhandled [ failure:

    $ ./bashunit --coverage-min abc tests/
    src/coverage.sh: line 1459: [: abc: integer expression expected
    ...
    $ echo $?
    0

    --shard is the exception: bashunit::main::set_shard_or_exit validates it and exits non-zero with a clear message. That is the pattern the rest should follow.

    The practical impact is the same shape as #871 — a CI job configured with --coverage-min 80 that gets a bad value reports success without enforcing the threshold.

  3. Chemaclass commented on Jul 26, 2026

    @Chemaclass
    MemberAuthor

    Done in #874 (merged).

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

  • Status
    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions