Skip to content

[Hackathon] fix(test): flags documented "with --history / --run / --all" are silently ignored without that flag (their siblings exit 5) #376

Open

Description

@protonjls

Verified against (commit SHA or released version)

commit 988c0355bb05631e9c6c1785e443937ff2a88c0e (main, v0.14.0)

Discord Username / User ID

1149447580477042769

What does this improvement do?

Many options say in their own --help that they only work alongside another flag ("with --history: …", "with --run, …", "with --all: …"). The CLI already has a rule for these. When the enabling flag is missing it refuses with exit 5 and a nextAction naming the fix, instead of quietly doing something else. The test result action states the reason:

// `--env` narrows the history list; on the latest-result path it would be
// silently ignored (same rule as `test run --filter` without `--all`).

That rule is applied today to test result --env, test create/create-batch --env, test run --filter, all four of test rerun's --all flags (#160 → #163), testlist run --report/--gh-output/--summary-file, test run --gh-output/--summary-file and ci doctor --timeout. But 14 options documented the same way are still accepted without their enabling flag. They exit 0 and change nothing:

command ignored without the enabling flag its help says
test result <id> --since, --source, --page-size, --cursor, --rerun, --no-rerun, --columns, --no-header "with --history: …"
test create --wait, --timeout "with --run, poll until terminal status" / "with --run --wait, max seconds to wait"
test create-batch --wait, --timeout, --max-concurrency "with --run, …"
test run <id> --allow-empty "with --all: exit 0 when the run dispatches ZERO tests"

What a user or agent gets:

  • test result test_x --since 24h prints the latest result, not the run history it asked for. Invalid values pass too: --page-size 0 and --since garbage exit 0.
  • test create --plan-from plan.json --wait (forgot --run) creates the test, runs nothing, and exits 0, the same exit code as a passing --run --wait. A CI step written that way goes green without running a test. Same for create-batch … --wait.
  • test run test_x --allow-empty exits 0 whatever the flag says, while test rerun test_x --allow-empty is refused with exit 5.

Reproduction (offline, --dry-run; plan.json is any valid FE plan, dir/ holds a copy):

# refused today: exit 5
testsprite test result test_x --env staging --dry-run --output json; echo "exit=$?"
testsprite test create --plan-from plan.json --env staging --dry-run --output json; echo "exit=$?"
testsprite test rerun test_x --allow-empty --dry-run --output json; echo "exit=$?"
# accepted and ignored: exit 0
testsprite test result test_x --since 24h --dry-run --output json; echo "exit=$?"
testsprite test result test_x --page-size 0 --dry-run --output json; echo "exit=$?"
testsprite test create --plan-from plan.json --wait --timeout 30 --dry-run --output json; echo "exit=$?"
testsprite test create-batch --plan-from-dir dir --max-concurrency 5 --dry-run --output json; echo "exit=$?"
testsprite test run test_x --allow-empty --dry-run --output json; echo "exit=$?"

Observed: 5, 5, 5, then 0, 0, 0, 0, 0. Each ignored invocation prints exactly what it prints without the flag. In the real (non-dry-run) path the checks sit in the command actions, before any request, so the result is the same: in runCreate, opts.wait is read only inside if (opts.run === true).

Expected. Each of the 14 is refused like its siblings: exit 5, VALIDATION_ERROR, details.field set to the flag, and a nextAction such as "--since only applies with --history (it filters the run list). Add --history, or remove --since."

Details / implementation notes

  • Where. Every guard goes in front of existing ones of the same shape, using the existing localValidationError(field, message):
    • test result action (src/commands/test.ts, next to the --env check): the other with --history: flags. --rerun/--no-rerun and --no-header need command.getOptionValueSource(…) === 'cli', because Commander gives negatable flags a default.
    • runCreate / runCreateBatch: --wait/--timeout (and --max-concurrency in the batch) when run !== true, next to the existing --env requires --run.
    • test run action: --allow-empty without --all, next to --filter only applies with --all, with the wording test rerun already uses.
  • Deliberately not included: test run --timeout and testlist run --timeout without --wait. Both commands can poll without --wait (an automatic environment tunnel), so --timeout is live there even though their help says "with --wait".
  • Tests: one case per flag in the existing spec of each command (test.test.ts / the create and run specs), asserting exit 5 and details.field, plus a control showing that the same flag with its enabling flag is still accepted. Each fails on main today.
  • Optional, to keep this from coming back: a table-driven test that walks the Commander tree, collects every option whose description starts with with --X, and checks it is refused without --X. The table would carry an explicit allow-list for the two live --timeout cases above.
  • Compatibility: a script that passes one of these flags without its enabling flag gets exit 5 instead of 0. That is the same trade fix(rerun): reject explicit IDs with --all and --status/--skip-terminal without --all #163 made for test rerun, and the nextAction names the fix.

I already hold #372–#374, so this is free for anyone to pick up once it's triaged.

Confirmations

  • I have searched existing issues and this is not a duplicate.
  • I have provided my Discord identity above for reward coordination.

Activity

  1. protonjls commented on Oct 11, 2026

    @protonjls
    Author

    One more of the same class, found after filing. testlist run accepts --report-file / --report-suite-name without --report junit. Its help describes --report-file exactly as test run / test rerun do ("output path for --report"), and those two refuse it through the shared assertJUnitReportOptions (src/lib/junit-report.ts):

    exit=5  testsprite test run --all --project proj_1 --wait --report-file out.xml --dry-run
        Flag `--report-file` is invalid: --report-file requires --report junit.
    exit=5  testsprite test rerun --all --project proj_1 --wait --report-suite-name foo --dry-run
        Flag `--report-suite-name` is invalid: --report-suite-name requires --report junit.
    exit=0  testsprite testlist run tl_1 --wait --report-file out.xml --dry-run
    exit=0  testsprite testlist run tl_1 --wait --report-suite-name foo --dry-run
    exit=0  testsprite testlist run tl_1 --wait --report junit --report-file "" --dry-run
    exit=5  testsprite test run --all --project proj_1 --wait --report junit --report-file "" --dry-run
        Flag `--report-file` is invalid: --report junit requires --report-file <path>.
    

    testlist run writes the file only if (opts.report === 'junit' && opts.reportFile !== undefined). So a CI step that forgets --report junit runs the whole list, exits 0, and leaves no JUnit file for the job's test-report step. An empty --report-file "" passes validation too and only fails at the write, after the poll. The fix is for validateTestlistRunOptions (src/commands/testlist.ts) to call the shared assertJUnitReportOptions instead of its own partial check. Tests would go next to '--report junit without --wait → VALIDATION_ERROR' in src/commands/testlist.spec.ts.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions