Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Enforce root-cause investigation before any fix—structured debugging protocol for bugs and test failures
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
condition-based-waiting.md
1# Condition-Based Waiting23## Overview45Flaky 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.67**Core principle:** Wait for the actual condition you care about, not a guess about how long it takes.89## When to Use1011```dot12digraph when_to_use {13"Test uses setTimeout/sleep?" [shape=diamond];14"Testing timing behavior?" [shape=diamond];15"Document WHY timeout needed" [shape=box];16"Use condition-based waiting" [shape=box];1718"Test uses setTimeout/sleep?" -> "Testing timing behavior?" [label="yes"];19"Testing timing behavior?" -> "Document WHY timeout needed" [label="yes"];20"Testing timing behavior?" -> "Use condition-based waiting" [label="no"];21}22```2324**Use when:**25- Tests have arbitrary delays (`setTimeout`, `sleep`, `time.sleep()`)26- Tests are flaky (pass sometimes, fail under load)27- Tests timeout when run in parallel28- Waiting for async operations to complete2930**Don't use when:**31- Testing actual timing behavior (debounce, throttle intervals)32- Always document WHY if using arbitrary timeout3334## Core Pattern3536```typescript37// ❌ BEFORE: Guessing at timing38await new Promise(r => setTimeout(r, 50));39const result = getResult();40expect(result).toBeDefined();4142// ✅ AFTER: Waiting for condition43await waitFor(() => getResult() !== undefined);44const result = getResult();45expect(result).toBeDefined();46```4748## Quick Patterns4950| Scenario | Pattern |51|----------|---------|52| Wait for event | `waitFor(() => events.find(e => e.type === 'DONE'))` |53| Wait for state | `waitFor(() => machine.state === 'ready')` |54| Wait for count | `waitFor(() => items.length >= 5)` |55| Wait for file | `waitFor(() => fs.existsSync(path))` |56| Complex condition | `waitFor(() => obj.ready && obj.value > 10)` |5758## Implementation5960Generic polling function:61```typescript62async function waitFor<T>(63condition: () => T | undefined | null | false,64description: string,65timeoutMs = 500066): Promise<T> {67const startTime = Date.now();6869while (true) {70const result = condition();71if (result) return result;7273if (Date.now() - startTime > timeoutMs) {74throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);75}7677await new Promise(r => setTimeout(r, 10)); // Poll every 10ms78}79}80```8182See `condition-based-waiting-example.ts` in this directory for complete implementation with domain-specific helpers (`waitForEvent`, `waitForEventCount`, `waitForEventMatch`) from actual debugging session.8384## Common Mistakes8586**❌ Polling too fast:** `setTimeout(check, 1)` - wastes CPU87**✅ Fix:** Poll every 10ms8889**❌ No timeout:** Loop forever if condition never met90**✅ Fix:** Always include timeout with clear error9192**❌ Stale data:** Cache state before loop93**✅ Fix:** Call getter inside loop for fresh data9495## When Arbitrary Timeout IS Correct9697```typescript98// Tool ticks every 100ms - need 2 ticks to verify partial output99await waitForEvent(manager, 'TOOL_STARTED'); // First: wait for condition100await new Promise(r => setTimeout(r, 200)); // Then: wait for timed behavior101// 200ms = 2 ticks at 100ms intervals - documented and justified102```103104**Requirements:**1051. First wait for triggering condition1062. Based on known timing (not guessing)1073. Comment explaining WHY108109## Real-World Impact110111From debugging session (2025-10-03):112- Fixed 15 flaky tests across 3 files113- Pass rate: 60% → 100%114- Execution time: 40% faster115- No more race conditions116