github/scientist · error · Scientist::BehaviorNotUnique

# already has # behavior

Error message

#{experiment.name} already has #{name} behavior

What it means

Scientist::BehaviorNotUnique is raised by Experiment#try when a behavior with the same name has already been registered on the experiment. Each experiment instance allows exactly one block per behavior name ("control" for `use`, "candidate" by default for `try`); duplicate registration raises instead of silently overwriting.

Solutions

  1. Use distinct names for each candidate: `try("candidate_a") { }`, `try("candidate_b") { }`.
  2. Ensure each registration method is called at most once per name per instance; guard with `behaviors.include?(name)` if unsure.
  3. Create a new experiment instance if you need to re-register the same-named behavior.
  4. Fix the code path that invokes the registration helper twice (e.g. a loop not creating fresh experiments per iteration).

Example fix

# before
exp.try { candidate_a }  # registers "candidate"
exp.try { candidate_b }  # BehaviorNotUnique

# after
exp.try("candidate_a") { candidate_a }
exp.try("candidate_b") { candidate_b }
exp.run
Defensive patterns

Strategy: validation

Validate before calling

raise "duplicate candidate #{name}" if experiment.send(:behaviors).key?(name.to_s)
experiment.try(name) { block }

Type guard

def can_register?(exp, name = "candidate")
  !exp.send(:behaviors).key?(name.to_s)
end

Try / catch

begin
  experiment.try(name) { candidate_block }
rescue Scientist::BehaviorNotUnique
  logger.warn("candidate #{name} already registered; skipping")
end

Prevention

When it happens

Trigger: Calling `experiment.try { ... }` twice without names (both register "candidate"); calling `experiment.try(:x) { }` twice; calling `use` twice (both register "control"); re-running a behavior-registration helper on the same experiment instance; named `try` calls that collide with an existing name.

Common situations: Loops or helper methods that register candidates and get invoked twice on the same experiment object; duplicate `use` calls from copy-paste; memoization mistakes that re-run registration code; framework integrations registering a behavior in both a DSL callback and setup code.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of github/scientist@504a396e98 (2026-09-13). Data as JSON: /api/errors/ed3ac0aef38e8b44. Report an issue: GitHub.

Appendix: source

Thrown at lib/scientist/experiment.rb:296

    return false
  end

  # Internal: determine whether or not an experiment should run.
  #
  # Rescues and reports exceptions in the enabled method if they occur.
  def should_experiment_run?
    behaviors.size > 1 && enabled? && run_if_block_allows?
  rescue StandardError => ex
    raised :enabled, ex
    return false
  end

  # Register a named behavior for this experiment, default "candidate".
  def try(name = nil, &block)
    name = (name || "candidate").to_s

    if behaviors.include?(name)
      raise Scientist::BehaviorNotUnique.new(self, name)
    end

    behaviors[name] = block
  end

  # Register the control behavior for this experiment.
  def use(&block)
    try "control", &block
  end

  # Whether or not to raise a mismatch error when a mismatch occurs.
  def raise_on_mismatches?
    if raise_on_mismatches.nil?
      self.class.raise_on_mismatches?
    else
      !!raise_on_mismatches
    end
  end

View on GitHub (pinned to 504a396e98)