Skip to content
Latchkey

Elixir "mix test" Flaky Async Failures in CI

ExUnit tests marked async: true run concurrently. A test that passes alone but fails intermittently in CI usually shares state with another async test, assumes ordering, or hits the database sandbox in the wrong mode.

What this error means

A mix test run fails intermittently - different tests on different runs, often with assertion mismatches or "owner ... not allowed" sandbox errors. Re-running passes, and the failures cluster around async: true modules.

mix output
1) test creates record (App.WorkerTest)
   Assertion failed, expected 1 record, got 2
# or
** (DBConnection.OwnershipError) cannot find ownership process for #PID<...>

Common causes

Shared mutable state across async tests

Two async: true tests touch the same global/ETS/registry/external resource. Running concurrently they interleave, producing nondeterministic results.

Sandbox not in shared mode for spawned processes

When a test spawns processes that hit the DB, the Ecto SQL sandbox must allow them; without shared mode under concurrency you get ownership errors.

How to fix it

Isolate state or drop async for the conflicting tests

  1. Identify tests that share a global resource and set those modules to async: false.
  2. Replace shared global state with per-test setup (unique names, fresh ETS tables).
  3. Avoid asserting on ordering that concurrency does not guarantee.

Configure the SQL sandbox correctly

Use the sandbox in a mode that supports the processes your test spawns.

Elixir
# test/support setup
:ok = Ecto.Adapters.SQL.Sandbox.checkout(App.Repo)
Ecto.Adapters.SQL.Sandbox.mode(App.Repo, {:shared, self()})

How to prevent it

  • Keep async: true tests free of shared mutable state.
  • Use unique per-test identifiers instead of fixed global names.
  • Configure the Ecto sandbox correctly for spawned processes.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →