Skip to content

Flaky: useTimelineMode live-poll resume fails when a leaked loadSource writes SCAN_PROGRESS #212

Description

@thalida

tests/hooks/useTimelineMode.test.ts > live poll suspends in Timeline mode > does not probe the signature endpoint while in Timeline mode, and resumes on exit fails intermittently on main.

AssertionError: expected "fetch" to be called at least once
  tests/hooks/useTimelineMode.test.ts:361:22

Twice in three weeks, both on main, both the same assertion:

Not reproducible on demand: 12/12 passes running the file alone, and the full suite with coverage passes 2153/2153 locally. It needs CI-level scheduling pressure.

Cause

TIMELINE_MODE never restarts the poll timer. setupLiveUpdates starts one interval and tick() early-returns while Timeline is on (app/src/hooks/useManifestSource.ts:286):

if (inFlight) return;
if (TIMELINE_MODE.peek()) return;
if (SCAN_PROGRESS.peek() !== null) return;   // <- this one

So the resume assertion needs a tick to land in a 1000ms window and clear all three guards. SCAN_PROGRESS is module-level, and an earlier test in the same file leaks a load that writes it.

exitTimelineMode (line 297) starts a live-HEAD reload and only asserts the stream opened:

exitTimelineMode();          // -> void loadSource(...), never awaited
await flush();
expect(StubEventSource.instances.length).toBeGreaterThan(0);

That loadSource outlives the test. pumpManifestStream writes SCAN_PROGRESS.value = {...} per stream event (useManifestSource.ts:100) and clears it only in finally (line 214). The flaky test's beforeEach nulls SCAN_PROGRESS, but the leaked stream writes it non-null again — and if that lands inside advanceTimersByTimeAsync, the guard fires and no fetch happens.

Load-dependent, which is why it survives isolation and only shows under a loaded CI run.

Fix

Make exitTimelineMode's test finish what it starts — drive the stub stream to completion and await it — so no async work outlives the test. Not a retry wrapper, and not a wider timer window: both just lower the odds without removing the leak.

Worth checking the other void-dispatched loads in the file for the same shape while in there.

Activity

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

Metadata

Metadata

Assignees

Labels

cat:bugSomething isn't working

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions