grosser/parallel_tests · error · RuntimeError

Can't pass --specify-groups with any of these keys: --single

Error message

Can't pass --specify-groups with any of these keys: --single, --isolate, or --isolate-n

What it means

--specify-groups pins exact specs to exact processes, while --single/-s, --isolate and --isolate-n also dictate which process certain tests land in. The two placement mechanisms would overwrite each other, so parse_options! rejects the combination by intersecting the options keys with [:single_process, :isolate, :isolate_count].

Source

Thrown at lib/parallel_tests/cli.rb:380

          end
        end
        options[:files] = files.map { |file_path| Pathname.new(file_path).cleanpath.to_s }
      end

      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..]

View on GitHub (pinned to a06047856d)

Solutions

  1. Remove --single/-s, --isolate and --isolate-n from the command
  2. If certain specs still must be isolated, express that inside --specify-groups by putting them alone in their own pipe-separated process group

Example fix

# before
parallel_test -n 3 --isolate --specify-groups 'a_spec.rb|b_spec.rb'

# after
parallel_test -n 3 --specify-groups 'a_spec.rb|b_spec.rb'
Defensive patterns

Strategy: validation

Validate before calling

opts = ENV.fetch('PARALLEL_TEST_OPTS', '').shellsplit
conflicting = opts.any? { |o| o == '-s' || o.start_with?('--single', '--isolate') } # --isolate-n matches the --isolate prefix
if opts.include?('--specify-groups') && conflicting
  abort 'parallel_tests: --specify-groups cannot be combined with --single/--isolate/--isolate-n'
end

Type guard

def specify_groups_args_valid?(argv)
  return true unless argv.any? { |a| a == '--specify-groups' || a.start_with?('--specify-groups=') }
  argv.none? { |a| a == '-s' || a.start_with?('--single', '--isolate') }
end

Try / catch

begin
  ParallelTests::CLI.new.run(args)
rescue RuntimeError => e
  abort "#{e.message} -- keep exactly one placement mechanism per command"
end

Prevention

When it happens

Trigger: `parallel_test -n 3 --specify-groups 'a_spec.rb,b_spec.rb|c_spec.rb' --isolate`, the same with `-s pattern` or `--isolate-n 2`, or a rake/CI option string containing --specify-groups together with any isolation flag.

Common situations: Teams migrate from --isolate/--single workflows to explicit group pinning (e.g. to debug flaky splits) and forget to strip the old flags; shared CI option lists merge flags from several jobs into one string.

Related errors


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