Two wrapper tests have failed intermittently on the macOS CI job over the last two days, each on a commit that touches neither the object nor its kernel, and each on a SHA that passed the same job on another run. They are the only two objects in the package whose constructor arms a self-rescheduling timer<>.
The two failures
| date |
SHA |
job |
result |
| 2026-08-16 |
08a9671 |
macos |
41 - tap.gardener_test (Failed) — 1 of 79 |
| 2026-08-18 |
6115aea |
macos |
19 - tap.bloom_test (Bus error) — 1 of 85 |
The bloom run is the informative one: a bus error is a crash, not an assertion failure, in a test whose entire body sets attributes and reads them back.
Both are on claude/radiohead-taptools-sfx-is456q, and neither tap.bloom, tap.gardener nor garden.h is in that branch's diff. 6115aea passed the macOS job on its push run (23:04 UTC) and failed on its pull_request run (01:47 UTC) — same tree, same job, different outcome. Linux and Windows have never failed either test.
Why these two, and probably why at all
tap.bloom and tap.gardener are the package's only objects that arm a repeating timer from their constructor:
// tap.bloom.cpp
timer<> m_clock{this, MIN_FUNCTION{
const int n = m_ring.due(m_fired.data(), kernel::k_max_events);
m_ring.step();
for (int i = 0; i < n; ++i) { m_out_note.send("note", ...); }
m_clock.delay(interval); // re-arms itself
return {};
}};
bloom(const atoms& args = {}) {
...
m_clock.delay(interval); // armed at construction
}
The wrapper tests build and destroy several test_wrapper<bloom> instances in quick succession, each of which arms a timer that keeps re-arming. If the harness services a timer whose object has been (or is being) destroyed, the callback touches m_ring, m_fired and m_out_note on a dead object — which is exactly the shape of an intermittent, platform-dependent bus error, and exactly why a test that only pokes attributes can crash.
This is a hypothesis, not a diagnosis. It fits every observation (the two affected objects are the two timer-armed ones; the failures are intermittent, timing-shaped and single-platform; the crash is a memory fault in a test that touches no memory of its own) but nobody has run it under a debugger.
Not reproducible here
tap.bloom_test was run 40 times consecutively on Linux with no failure. Whatever the race is, it needs macOS's scheduler, or a CI runner's load, or both.
Suggested next steps
- Run the two tests in a loop on a Mac, ideally under Address/Thread Sanitizer, to convert the hypothesis into a stack trace.
- If it is the destruction race: either do not arm the clock until the object is fully constructed and stop it in a destructor, or have the tests construct these two objects with the clock disarmed.
- Until then, a re-run clears it — which is the wrong answer if it recurs, since a bus error in CI is a real crash in shipping code paths, not test noise.
Filed rather than fixed because it is unrelated to the branch it surfaced on, and a timer-lifetime change in two shipped objects deserves its own change with its own verification.
Two wrapper tests have failed intermittently on the macOS CI job over the last two days, each on a commit that touches neither the object nor its kernel, and each on a SHA that passed the same job on another run. They are the only two objects in the package whose constructor arms a self-rescheduling
timer<>.The two failures
08a967141 - tap.gardener_test (Failed)— 1 of 796115aea19 - tap.bloom_test (Bus error)— 1 of 85The bloom run is the informative one: a bus error is a crash, not an assertion failure, in a test whose entire body sets attributes and reads them back.
Both are on
claude/radiohead-taptools-sfx-is456q, and neithertap.bloom,tap.gardenernorgarden.his in that branch's diff.6115aeapassed the macOS job on its push run (23:04 UTC) and failed on its pull_request run (01:47 UTC) — same tree, same job, different outcome. Linux and Windows have never failed either test.Why these two, and probably why at all
tap.bloomandtap.gardenerare the package's only objects that arm a repeating timer from their constructor:The wrapper tests build and destroy several
test_wrapper<bloom>instances in quick succession, each of which arms a timer that keeps re-arming. If the harness services a timer whose object has been (or is being) destroyed, the callback touchesm_ring,m_firedandm_out_noteon a dead object — which is exactly the shape of an intermittent, platform-dependent bus error, and exactly why a test that only pokes attributes can crash.This is a hypothesis, not a diagnosis. It fits every observation (the two affected objects are the two timer-armed ones; the failures are intermittent, timing-shaped and single-platform; the crash is a memory fault in a test that touches no memory of its own) but nobody has run it under a debugger.
Not reproducible here
tap.bloom_testwas run 40 times consecutively on Linux with no failure. Whatever the race is, it needs macOS's scheduler, or a CI runner's load, or both.Suggested next steps
Filed rather than fixed because it is unrelated to the branch it surfaced on, and a timer-lifetime change in two shipped objects deserves its own change with its own verification.