grosser/parallel_tests · error · RuntimeError

Can't pass --failure-exit-code and --highest-exit-status

Error message

Can't pass --failure-exit-code and --highest-exit-status

What it means

Both flags define what exit status parallel_tests itself returns when a child test process fails: --failure-exit-code forces a constant you choose, while --highest-exit-status propagates the largest child exit code. They answer the same question differently, so parse_options! raises when both are present.

Source

Thrown at lib/parallel_tests/cli.rb:384

      append_test_options(options, remaining)

      options[:group_by] ||= :filesize if options[:only_group]

      if options[:group_by] == :found && options[:single_process]
        raise "--group-by found and --single-process are not supported"
      end
      allowed = [:filesize, :runtime, :found]
      if !allowed.include?(options[:group_by]) && options[:only_group]
        raise "--group-by #{allowed.join(" or ")} is required for --only-group"
      end

      if options[:specify_groups] && options.keys.intersect?([:single_process, :isolate, :isolate_count])
        raise "Can't pass --specify-groups with any of these keys: --single, --isolate, or --isolate-n"
      end

      if options[:failure_exit_code] && options[:highest_exit_status]
        raise "Can't pass --failure-exit-code and --highest-exit-status"
      end

      options
    end

    def extract_file_paths(argv)
      dash_index = argv.rindex("--")
      file_args_at = (dash_index || -1) + 1
      [argv[file_args_at..], argv[0...(dash_index || 0)]]
    end

    def extract_test_options(argv)
      dash_index = argv.index("--") || -1
      argv[dash_index + 1..]
    end

    def append_test_options(options, argv)
      new_opts = extract_test_options(argv)

View on GitHub (pinned to a06047856d)

Solutions

  1. Pick one strategy: keep --failure-exit-code alone for a fixed CI-recognizable code, or --highest-exit-status alone to propagate child codes
  2. If the intent was to configure RSpec's own exit code, pass it through to the runner instead: `parallel_test --highest-exit-status -o '--failure-exit-code 42'`
  3. Prune the duplicated flag from rake tasks / CI env where it crept in

Example fix

# before
parallel_test --highest-exit-status --failure-exit-code 42

# after (fixed code)
parallel_test --failure-exit-code 42
# after (RSPEC's own flag passed through)
parallel_test --highest-exit-status -o '--failure-exit-code 42'
Defensive patterns

Strategy: validation

Validate before calling

opts = ENV.fetch('PARALLEL_TEST_OPTS', '').shellsplit
if opts.include?('--failure-exit-code') && opts.include?('--highest-exit-status')
  abort 'parallel_tests: choose either --failure-exit-code or --highest-exit-status, not both'
end

Type guard

def exit_status_args_valid?(argv)
  !(argv.include?('--failure-exit-code') && argv.include?('--highest-exit-status'))
end

Try / catch

begin
  ParallelTests::CLI.new.run(args)
rescue RuntimeError => e
  abort "#{e.message} -- pass RSpec's own --failure-exit-code after -- or via -o if that was intended"
end

Prevention

When it happens

Trigger: `parallel_test --failure-exit-code 42 --highest-exit-status`, or a rake/CI string like PARALLEL_TEST_OPTS="--highest-exit-status --failure-exit-code 1".

Common situations: CI pipelines that already use --highest-exit-status (to distinguish RSpec failure codes from crashes) gain --failure-exit-code when someone copies an RSpec invocation -- RSpec itself accepts a flag of the same name, and passing it after `--` (or via -o as a test option) is legal, which feeds the confusion.

Related errors


AI-assisted analysis of grosser/parallel_tests@a06047856d (2026-08-23). Data as JSON: /api/errors/ca6d071dc7f952ff. Report an issue: GitHub.