skills/condition-based-waiting/SKILL.md
Use when tests have race conditions, timing dependencies, or pass and fail inconsistently, especially when tests use arbitrary delays like setTimeout or sleep
npx skillsauth add BubbleBuffer/superpawers condition-based-waitingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
digraph 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:
// 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:
async 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));
}
}
See condition-based-waiting-example.ts in this directory for a complete implementation with domain-specific helpers (waitForEvent, waitForEventCount, waitForEventMatch).
| Mistake | Fix |
|---------|-----|
| Polling too fast: setTimeout(check, 1) | Poll every 10ms |
| No timeout: loop forever | Always include timeout with clear error |
| Stale data: cache state before loop | Call getter inside loop for fresh data |
// 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:
data-ai
Use when a request involves multiple steps or files, or when an approved design must be turned into a detailed implementation plan
development
Use when deciding which SuperPawers skill should govern a new task or workflow step, before taking any other action
development
Use when starting feature work that needs git isolation or before writing committed spec, plan, or code artifacts
development
Use when a task list exists or is being created for multi-step implementation work, whether from a formal plan or an ad-hoc breakdown