Skip to content

test(cli): tui-mcp-control's local waitFor spins 1000 immediates with no wall-clock budget, flaky under CI load #4094

Description

@Astro-Han

What happened

CI failed on PR #4090, a change whose diff touches only packages/ui/src/chat-turn.tsx and packages/ui/src/styles.css. The failing job was the core test job, in the [cli] workspace:

✖ same-server credential retirement stays inside the shared config transaction (40.337964ms)
  AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
    assert.ok(condition())
      at waitFor (file:///.../packages/cli/dist/__tests__/tui-mcp-control.test.js:710:12)
      at async TestContext.<anonymous> (file:///.../packages/cli/dist/__tests__/tui-mcp-control.test.js:471:5)

main was green at the time, and the same test passed in #4062's own merge run. @maka/cli does not depend on @maka/ui; the cli suite ran because the affected-surface planner validates the merged delta, which included #4062's files.

Root cause

tui-mcp-control.test.ts (added by #4062) defines a local waitFor:

async function waitFor(condition: () => boolean): Promise<void> {
  for (let attempt = 0; attempt < 1_000 && !condition(); attempt += 1) {
    await new Promise<void>((resolve) => setImmediate(resolve));
  }
  assert.ok(condition());
}

The budget is an iteration count, not a wall-clock duration. 1000 setImmediate spins burn out in tens of milliseconds — the reported test duration was 40.3 ms — while the predicate being awaited (leftOrder.includes('forget:docs')) needs a real filesystem config transaction in a mkdtemp directory to complete. On a loaded runner running three workspaces in parallel, the spin budget expires before the I/O lands, and the failure surfaces as an opaque assert.ok(condition()) with no description of what was being waited on.

This is the mirror image of #2221: that issue fixed a fixed 250 ms wall-clock deadline that flaked under CI load, and #2304 established the repo's settled pattern in tui-terminal-mock.tsWAIT_BUDGET_MS = 250 ms local / 5 000 ms CI, overridable via MAKA_TEST_WAIT_BUDGET_MS, with a failure message that names what was being awaited. #4062's new file reinvented a local helper that repeats the same class of mistake in the opposite direction: where a fixed short wall-clock budget is load-sensitive, an iteration budget is even more so.

The helper has 28 call sites in this one file; any of them can lose the same race.

Why it matters

Same cost as #2221: the failure is indistinguishable from a real regression at the point of reading CI. An unrelated ui-only PR showed a red test check, and telling the two apart required pulling the job log and re-running. Every PR whose merged delta includes packages/cli pays this until the helper is fixed.

Suggested direction

Not prescribing a fix, but the established seam already exists: reuse the #2304 pattern — import the shared time-budgeted waitFor from tui-terminal-mock.ts (or extract it into a shared test-wait module if the harness dependency reads wrong), so every TUI wait shares one budget, one env override, and descriptive expiry messages. An iteration-count budget should not come back: it measures event-loop enthusiasm, not elapsed time.

Repro

Not deterministically reproducible by design — it is a load-dependent race. Observed on the core test job in run 33177032826; main was green at the same commit range.


Filed with AI assistance (Maka): the agent traced the failing run, the planner output, and both waitFor implementations; the human contributor reviewed this report before filing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghelp wantedExtra attention is needed

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions