Context
E2E — Elixir Scanner Pipeline is red on main. With #642 making the output visible, the
cause is now measurable rather than mysterious.
Reproduced locally (Elixir 1.18.3 / OTP 27, mix deps.get clean):
| Run |
Result |
mix test (full suite) |
1398 tests, 20 failures |
mix test test/reflexive_test.exs (alone) |
16 tests, 0 failures |
Every one of the 20 failures is in test/reflexive_test.exs with the same shape:
** (exit) exited in: GenServer.call(Hypatia.Supervisor, {:terminate_child, Hypatia.SelfDiagnostics}, :infinity)
** (EXIT) no process: the process is not alive or there's no process currently
associated with the given name, possibly because its application isn't started
test/reflexive_test.exs:29: Hypatia.ReflexiveTest.take_supervised/1
The file passing in isolation is the key datum: this is a test-isolation defect, not a
product bug. Hypatia.Supervisor is alive at the start of an isolated run and dead by the
time reflexive tests run in the full suite.
Likely mechanism (to be confirmed, not assumed)
take_supervised/1 (test/reflexive_test.exs:29) removes a child from the live application
supervisor so start_supervised!/1 can own a clean instance:
Supervisor.terminate_child(Hypatia.Supervisor, module)
Supervisor.delete_child(Hypatia.Supervisor, module)
start_supervised!(module)
on_exit(fn -> ... Supervisor.start_child(Hypatia.Supervisor, module) end)
Two candidate causes, both consistent with the evidence:
- Restore never happens. If a test crashes before
on_exit runs, or start_child
fails (the spec was deleted), the child stays absent and later take_supervised calls
compound the damage.
max_restarts exceeded. lib/application.ex:90 uses strategy: :one_for_one with
the default max_restarts: 3, max_seconds: 5. Repeated terminate/restart churn across
many tests — plus any child that crash-loops — trips the limit and the whole supervisor
shuts down, taking Hypatia.Supervisor with it. This matches "no process ... its
application isn't started" exactly.
Hypatia.ReflexiveTest is async: false, so ordering is deterministic per seed — the failure
should be reproducible with a fixed --seed.
Changes
Diagnosis first — do not guess at a fix:
- Confirm which of the two mechanisms applies: run the full suite with a fixed
--seed,
and assert Process.whereis(Hypatia.Supervisor) in setup to find the first test at
which it is already nil. Bisect by test file.
- Then fix at the right level:
- If restore-failure: make
take_supervised/1 restore idempotently and register the
on_exit before the mutation, so a crash mid-setup still restores.
- If
max_restarts: stop mutating the live application supervisor from tests at all.
Preferred shape — start these GenServers under the test's own supervisor via
start_supervised!/1 with an explicit name:, and leave Hypatia.Supervisor
untouched. A test should not need to dismantle the application to observe it.
- Consider raising
max_restarts in lib/application.ex:90 only if it is genuinely
warranted in production — not as a way to paper over test churn.
Acceptance criteria
mix test is 0 failures on a clean checkout, at three different seeds.
mix test test/reflexive_test.exs still passes in isolation (16 tests).
- No test terminates or deletes a child of
Hypatia.Supervisor, or if it must, the
restore is proven by asserting the child is alive again after the suite.
E2E — Elixir Scanner Pipeline green on main.
Test plan
Model tier
Sonnet — bounded, single-repo, no new design; the mechanism is narrowed to two
candidates and the acceptance criteria are mechanical. Escalate to Opus only if the
cause turns out to be a genuine supervision-tree design problem rather than test hygiene.
Related
Context
E2E — Elixir Scanner Pipelineis red onmain. With #642 making the output visible, thecause is now measurable rather than mysterious.
Reproduced locally (Elixir 1.18.3 / OTP 27,
mix deps.getclean):mix test(full suite)mix test test/reflexive_test.exs(alone)Every one of the 20 failures is in
test/reflexive_test.exswith the same shape:The file passing in isolation is the key datum: this is a test-isolation defect, not a
product bug.
Hypatia.Supervisoris alive at the start of an isolated run and dead by thetime reflexive tests run in the full suite.
Likely mechanism (to be confirmed, not assumed)
take_supervised/1(test/reflexive_test.exs:29) removes a child from the live applicationsupervisor so
start_supervised!/1can own a clean instance:Two candidate causes, both consistent with the evidence:
on_exitruns, orstart_childfails (the spec was deleted), the child stays absent and later
take_supervisedcallscompound the damage.
max_restartsexceeded.lib/application.ex:90usesstrategy: :one_for_onewiththe default
max_restarts: 3, max_seconds: 5. Repeated terminate/restart churn acrossmany tests — plus any child that crash-loops — trips the limit and the whole supervisor
shuts down, taking
Hypatia.Supervisorwith it. This matches "no process ... itsapplication isn't started" exactly.
Hypatia.ReflexiveTestisasync: false, so ordering is deterministic per seed — the failureshould be reproducible with a fixed
--seed.Changes
Diagnosis first — do not guess at a fix:
--seed,and assert
Process.whereis(Hypatia.Supervisor)insetupto find the first test atwhich it is already
nil. Bisect by test file.take_supervised/1restore idempotently and register theon_exitbefore the mutation, so a crash mid-setup still restores.max_restarts: stop mutating the live application supervisor from tests at all.Preferred shape — start these GenServers under the test's own supervisor via
start_supervised!/1with an explicitname:, and leaveHypatia.Supervisoruntouched. A test should not need to dismantle the application to observe it.
max_restartsinlib/application.ex:90only if it is genuinelywarranted in production — not as a way to paper over test churn.
Acceptance criteria
mix testis 0 failures on a clean checkout, at three different seeds.mix test test/reflexive_test.exsstill passes in isolation (16 tests).Hypatia.Supervisor, or if it must, therestore is proven by asserting the child is alive again after the suite.
E2E — Elixir Scanner Pipelinegreen onmain.Test plan
Process.whereis(Hypatia.Supervisor)is non-nil at theend of
reflexive_test.exs.mix testat seeds 0, 1, and a random one — all green.Model tier
Sonnet — bounded, single-repo, no new design; the mechanism is narrowed to two
candidates and the acceptance criteria are mechanical. Escalate to Opus only if the
cause turns out to be a genuine supervision-tree design problem rather than test hygiene.
Related
e2e.shwas discarding the output).tests.yml(arangodb health check); independent cause.lib/application.ex:90— the:one_for_one/max_restartssite.test/reflexive_test.exs:29—take_supervised/1.