Skip to content

Flaky on macOS CI: the timer-driven wrapper tests (tap.bloom, tap.gardener) fail intermittently, once with a bus error #68

Description

@tap

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions