Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when tests have race conditions, timing dependencies, or inconsistent pass/fail behavior - replaces arbitrary timeouts with condition polling to wait for actual state changes, eliminating flaky tests from timing guesses
.claude/skills/microck-condition-based-waiting/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 29% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 35% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 1% | 0% |
| case-10 | ✗→✓ | ▲ Improved | -22% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 12% | 0% |
Flaky tests often guess at timing with arbitrary delays. This creates race conditions where tests pass on fast machines but fail under load or in CI.
Core principle: Wait for the actual condition you care about, not a guess about how long it takes.
dotdigraph when_to_use { "Test uses setTimeout/sleep?" [shape=diamond]; "Testing timing behavior?" [shape=diamond]; "Document WHY timeout needed" [shape=box]; "Use condition-based waiting" [shape=box]; "Test uses setTimeout/sleep?" -> "Testing timing behavior?" [label="yes"]; "Testing timing behavior?" -> "Document WHY timeout needed" [label="yes"]; "Testing timing behavior?" -> "Use condition-based waiting" [label="no"]; }
Use when:
setTimeout, sleep, time.sleep())Don't use when:
typescript// ❌ BEFORE: Guessing at timing await new Promise(r => setTimeout(r, 50)); const result = getResult(); expect(result).toBeDefined(); // ✅ AFTER: Waiting for condition await waitFor(() => getResult() !== undefined); const result = getResult(); expect(result).toBeDefined();
| Scenario | Pattern | |----------|---------| | Wait for event | waitFor(() => events.find(e => e.type === 'DONE')) | | Wait for state | waitFor(() => machine.state === 'ready') | | Wait for count | waitFor(() => items.length >= 5) | | Wait for file | waitFor(() => fs.existsSync(path)) | | Complex condition | waitFor(() => obj.ready && obj.value > 10) |
Generic polling function:
typescriptasync function waitFor<T>( condition: () => T | undefined | null | false, description: string, timeoutMs = 5000 ): Promise<T> { const startTime = Date.now(); while (true) { const result = condition(); if (result) return result; if (Date.now() - startTime > timeoutMs) { throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`); } await new Promise(r => setTimeout(r, 10)); // Poll every 10ms } }
See @example.ts for complete implementation with domain-specific helpers (waitForEvent, waitForEventCount, waitForEventMatch) from actual debugging session.
❌ Polling too fast: setTimeout(check, 1) - wastes CPU ✅ Fix: Poll every 10ms
❌ No timeout: Loop forever if condition never met ✅ Fix: Always include timeout with clear error
❌ Stale data: Cache state before loop ✅ Fix: Call getter inside loop for fresh data
typescript// Tool ticks every 100ms - need 2 ticks to verify partial output await waitForEvent(manager, 'TOOL_STARTED'); // First: wait for condition await new Promise(r => setTimeout(r, 200)); // Then: wait for timed behavior // 200ms = 2 ticks at 100ms intervals - documented and justified
Requirements:
From debugging session (2025-10-03):
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | fail→pass | 14,971 | 13,464 | -10% | 1 | 1 | 0% | 2,395 | 3,099 | +29% | 0 | 0 | — |
case-01 | fail→pass | 12,018 | 10,003 | -17% | 1 | 1 | 0% | 2,097 | 2,821 | +35% | 0 | 0 | — |
case-08 | fail→pass | 14,514 | 9,821 | -32% | 1 | 1 | 0% | 2,465 | 2,501 | +1% | 0 | 0 | — |
case-03 | pass→pass | 8,498 | 5,886 | -31% | 1 | 1 | 0% | 1,615 | 1,924 | +19% | 0 | 0 | — |
case-04 | pass→pass | 6,412 | 4,279 | -33% | 1 | 1 | 0% | 1,166 | 1,712 | +47% | 0 | 0 | — |
case-05 | pass→pass | 13,050 | 9,126 | -30% | 1 | 1 | 0% | 2,279 | 2,569 | +13% | 0 | 0 | — |
case-06 | pass→pass | 13,878 | 12,668 | -9% | 1 | 1 | 0% | 2,618 | 3,246 | +24% | 0 | 0 | — |
case-07 | pass→pass | 11,210 | 6,564 | -41% | 1 | 1 | 0% | 1,829 | 1,994 | +9% | 0 | 0 | — |
case-09 | pass→pass | 14,309 | 9,216 | -36% | 1 | 1 | 0% | 2,556 | 2,596 | +2% | 0 | 0 | — |
case-10 | fail→pass | 12,531 | 4,871 | -61% | 1 | 1 | 0% | 2,260 | 1,771 | -22% | 0 | 0 | — |
case-11 | pass→pass | 9,654 | 5,476 | -43% | 1 | 1 | 0% | 1,682 | 2,042 | +21% | 0 | 0 | — |
case-12 | pass→pass | 9,016 | 5,821 | -35% | 1 | 1 | 0% | 1,645 | 1,973 | +20% | 0 | 0 | — |
case-13 | fail→pass | 10,551 | 6,685 | -37% | 1 | 1 | 0% | 1,832 | 2,045 | +12% | 0 | 0 | — |
case-14 | pass→pass | 11,540 | 3,180 | -72% | 1 | 1 | 0% | 1,946 | 1,420 | -27% | 0 | 0 | — |
case-15 | pass→pass | 11,874 | 9,110 | -23% | 1 | 1 | 0% | 1,890 | 2,403 | +27% | 0 | 0 | — |
case-16 | pass→pass | 17,164 | 8,952 | -48% | 1 | 1 | 0% | 2,686 | 2,365 | -12% | 0 | 0 | — |
case-17 | fail→pass | 10,964 | 5,096 | -54% | 1 | 1 | 0% | 2,106 | 1,875 | -11% | 0 | 0 | — |
case-18 | pass→pass | 7,680 | 4,582 | -40% | 1 | 1 | 0% | 1,184 | 1,686 | +42% | 0 | 0 | — |
case-19 | pass→pass | 12,618 | 10,209 | -19% | 1 | 1 | 0% | 2,156 | 2,711 | +26% | 0 | 0 | — |
case-20 | pass→pass | 10,814 | 7,782 | -28% | 1 | 1 | 0% | 1,977 | 2,314 | +17% | 0 | 0 | — |
case-21 | pass→pass | 11,620 | 9,366 | -19% | 1 | 1 | 0% | 1,983 | 2,386 | +20% | 0 | 0 | — |
case-22 | pass→pass | 5,172 | 3,894 | -25% | 1 | 1 | 0% | 1,067 | 1,631 | +53% | 0 | 0 | — |
case-23 | fail→pass | 13,440 | 4,128 | -69% | 1 | 1 | 0% | 2,166 | 1,645 | -24% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +30 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.