#2062·garak

CI calculation reseeds the global numpy RNG, so --seed makes later numpy draws repeat

Author: chiruu12Created Aug 14, 2026Updated Sep 19, 2026
Labelsbugneeds-triage

_bootstrap_calculation in garak/analyze/bootstrap_ci.py seeds the global numpy generator when a run seed is set:

python
if (
    hasattr(_config, "run")
    and hasattr(_config.run, "seed")
    and _config.run.seed is not None
):
    np.random.seed(_config.run.seed)

garak/evaluators/base.py:149 calls this once per probe per detector, and confidence_interval_method defaults to bootstrap. So on any seeded run the global numpy RNG is reset back to the same value repeatedly during evaluation, and every numpy draw after that point restarts from the same state.

Reproduction:

python
from garak import _config
import garak.analyze.bootstrap_ci as b
import numpy as np

_config.load_base_config()
_config.run.seed = 42

def draw():
    return np.random.choice(10, 3).tolist()

np.random.seed(1234)
for _ in range(3):
    b.calculate_bootstrap_ci(results=[0]*60+[1]*40, sensitivity=0.95, specificity=0.90)
    print(draw())
[9, 1, 7]
[9, 1, 7]
[9, 1, 7]

Without the CI call in between, the same three draws are [4, 8, 9], [1, 7, 9], [6, 8, 0].

Two places in a run consume that generator:

  • garak/resources/red_team/conversation.py:185 shuffles branches before pruning, to permute equal-scoring elements. With the state pinned, ties break the same way every round.
  • garak/resources/autodan/genetic.py:193 picks parents for the genetic algorithm. Pinned state means the same parents are selected each generation.

So passing --seed, which is there to make a run reproducible, currently also removes the randomness TAP and AutoDAN rely on to explore. The bootstrap intervals themselves are affected too, since every probe and detector pair resamples with the same draws rather than independently.

Fix is to draw from a local generator rather than seeding the global one. np.random.default_rng(seed) keeps the interval reproducible for a given seed and drops the side effect. I have this working locally with a regression test, and the existing 14 tests in tests/analyze/test_bootstrap_ci.py still pass, including test_calculate_bootstrap_ci_reproducibility.

I would like to work on this, PR to follow.