The Drift Log · Model independence and repeatable builds
Seeding Entropy for Stable Test Runs in CI
9 September 2026 · 3 min read · 495 words · established

Capture PRNG seeds and clock offsets in CI to keep dynamic test inputs reproducible without resorting to static, bug-masking fixtures.
Run #842 passes. Run #843 fails on a commit that touched only documentation. The diff is clean, but an assertion failed because a randomly generated identifier collided with a reserved keyword, or a relative timestamp crossed midnight UTC between test setup and assertion.
You re-run the job. It passes. You merge the pull request and move on.
This is the standard lifecycle of test flakiness. The root cause is unmanaged entropy. When tests draw directly from unseeded system randomness and unanchored system clocks, they create transient edge cases that cannot be easily reproduced.
The common workaround—replacing all dynamic data with static strings like "test-user" and static dates—solves the flake but hides genuine bugs. It masks collisions, length boundaries, and time zone edge cases. The correct approach is to keep variable inputs, but capture and seed all entropy at the start of the CI run.
Capturing Seeds and Clock Offsets
A reliable test run requires two inputs to be deterministic: the pseudo-random number generator (PRNG) and the system clock.
At the start of your CI run, generate a single integer for the seed and capture a fixed reference timestamp for the epoch. Print both values to the job output before executing the runner:
TEST_SEED=${TEST_SEED:-$(od -vAn -N4 -tu4 < /dev/urandom | tr -d ' ')}
TEST_EPOCH=${TEST_EPOCH:-$(date +%s)}
echo "Running suite with TEST_SEED=${TEST_SEED} TEST_EPOCH=${TEST_EPOCH}"
export TEST_SEED TEST_EPOCH
Inside your test environment, initialize a fast PRNG (such as Mulberry32 or SplitMix32) using TEST_SEED. Route all dynamic test factories, fuzzers, and fake data generators through this seeded instance instead of global runtime randomness.
// test-entropy.ts
function mulberry32(seed: number) {
return function() {
let t = seed += 0x6D2B79F5;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
export const random = mulberry32(Number(process.env.TEST_SEED) || 42);
Parallel execution requires an extra step. When runners execute tests across worker processes, non-deterministic worker scheduling will interleave calls to a single shared sequence. If workers initialize from the same raw seed independently, they execute identical pseudorandom streams. To isolate worker state, derive a sub-seed deterministically for each file or worker—for instance, by hashing TEST_SEED alongside the test file's relative path.
For clocks, use your test runner's fake timer API to pin the global clock to TEST_EPOCH at the beginning of each test file, advancing it explicitly only when simulating timeouts or asynchronous delays.
Reproducible CI Without Masking Failure Surface
Deterministic tests do not require static data. By combining dynamic data generation with seeded randomness, each CI run tests a slightly different set of permutations across commits, exercising edge cases that static fixtures miss.
When a run fails, the seed is already logged in the CI output. Reproducing the failure locally does not require guessing:
TEST_SEED=284910482 TEST_EPOCH=1710000000 npm test
The test runner will replay the exact sequence of generated strings, numbers, UUIDs, and timestamps that produced the assertion error. Once fixed, you can add that specific seed to a regression test suite or verify the fix against the exact conditions that caught the bug.
Eliminating unmanaged variance is fundamental to reliable verification. As we discuss in our post on model independence as an engineering posture, relying on components that introduce unrepeatable runtime variance undermines your test harness. When entropy is explicitly bounded and repeatable, test failures point to real defects in logic rather than ambient environment drift.
This post supports the longer argument in Why Model Independence Is an Engineering Posture.
Keep reading
Next in the log
- Why Model Independence Is an Engineering Posture
Replacing parsers with runtime LLM calls trades deterministic invariants for statistical tendencies. Logic must be owned at runtime.
- Eliminating Timestamp Drift in Binary Release Checksums
Use GNU tar’s deterministic flags to eliminate timestamp drift and achieve reproducible release archives across machines.
- Isolating Network Ordering in Asynchronous Test Suites
Eliminate asynchronous test flakiness by swapping kernel loopback sockets for caller-stepped virtual transports and deterministic queues.
The Strategic Master Library · written and reviewed under the house's own epistemic rules: nothing claimed that we cannot show.