condition-based-waiting — Claude Code skill: setup and details
Replaces hardcoded timeouts like sleep or setTimeout with condition polling in async tests. Eliminates flakiness caused by execution timing.
npx skills add bobmatnyc/claude-mpm-skills --skill condition-based-waiting -a claude-codeWhat condition-based-waiting does
This skill guides the coding assistant to refactor async test suites. Instead of arbitrary delays like sleep() or time.sleep(), it applies a generic waitFor polling loop. The function checks a condition every 10ms until it resolves or hits a 5000ms timeout threshold.
It includes patterns for event streams and file creation checks. The instructions provide specialized helpers such as waitForEvent and waitForEventCount. Tests stop failing on busy systems.
Do not apply this pattern when testing debounce or throttle behavior. In those cases, verifying time intervals is part of the application contract.
What it helps with
- eliminate flaky test failures under CI load
- replace fixed sleep calls with predicate polling
- prevent race conditions in parallel test runs
- wait for file or event readiness reliably
Example prompts for the agent
- Refactor sleep calls in this test file to use condition polling
- Rewrite this flaky event test with a waitFor helper
- Replace fixed timeouts with condition-based waiting in async tests
How to install condition-based-waiting
1Skills CLI (Vercel)
npx skills add bobmatnyc/claude-mpm-skills --skill condition-based-waiting -a claude-code2Manually: put the files in place
~/.claude/skills/condition-based-waiting/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/condition-based-waiting/
1Skills CLI (Vercel)
npx skills add bobmatnyc/claude-mpm-skills --skill condition-based-waiting -a codex2Manually: put the files in place
.agents/skills/condition-based-waiting/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add bobmatnyc/claude-mpm-skills --skill condition-based-waiting -a cursor2Manually: put the files in place
.cursor/skills/condition-based-waiting/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install bobmatnyc/claude-mpm-skills condition-based-waiting2Manually: put the files in place
.github/skills/condition-based-waiting/SKILL.md1Skills CLI (Vercel)
npx skills add bobmatnyc/claude-mpm-skills --skill condition-based-waiting -a gemini-cli2Platform CLI
gemini skills install https://github.com/bobmatnyc/claude-mpm-skills --path universal/testing/condition-based-waitingManually: put the files in place
.devin/skills/condition-based-waiting/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add bobmatnyc/claude-mpm-skills --skill condition-based-waiting -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about condition-based-waiting
How to install condition-based-waiting in Claude Code?
Run npx skills add bobmatnyc/claude-mpm-skills --skill condition-based-waiting -a claude-code. The skill file installs to ~/.claude/skills/condition-based-waiting/SKILL.md.
What does condition-based-waiting do?
It provides guidelines to rewrite arbitrary delays into polling loops that check for exact conditions. This stops async tests from failing randomly on busy environments.
When should arbitrary timeouts be kept?
Keep them when directly testing timing constraints like debounce and throttle intervals. In those tests, measuring real delay durations is the intended target.
Source text
SKILL.md · MIT
---
name: condition-based-waiting
description: Replace arbitrary timeouts with condition polling for reliable async tests
user-invocable: false
disable-model-invocation: true
when_to_use: when tests have race conditions, timing dependencies, or inconsistent pass/fail behavior
version: 1.1.0
languages: all
progressive_disclosure:
entry_point:
summary: "Replace arbitrary timeouts with condition polling for reliable async tests"
when_to_use: "Tests with setTimeout/sleep, flaky tests, or timing-dependent async operations"
quick_start: |
1. Identify arbitrary delays in tests (setTimeout, sleep, time.sleep())
2. Replace with condition-based waiting (waitFor pattern)
3. Use domain-specific helpers for common scenarios
See @example.ts for complete working implementation
core_pattern: |
// ❌ Guessing at timing
await new Promise(r => setTimeout(r, 50));
// ✅ Waiting for condition
await waitFor(() => getResult() !== undefined);
references:
- path: references/patterns-and-implementation.md
purpose: Detailed waiting patterns, implementation guide, and common mistakes
when_to_read: When implementing waitFor or debugging timing issues
---
# Condition-Based Waiting
## Overview
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.
## When to Use
```dot
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:**
- Tests have arbitrary delays (`setTimeout`, `sleep`, `time.sleep()`)
- Tests are flaky (pass sometimes, fail under load)
- Tests timeout when run in parallel
- Waiting for async operations to complete
**Don't use when:**
- Testing actual timing behavior (debounce, throttle intervals)
- Always document WHY if using arbitrary timeout
## Core Pattern
```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();
```
## Quick Patterns
| 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)` |
## Implementation
Generic polling function:
```typescript
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)); // Poll every 10ms
}
}
```
See @example.ts for complete implementation with domain-specific helpers (`waitForEvent`, `waitForEventCount`, `waitForEventMatch`).
For detailed patterns, implementation guide, and common mistakes, see @references/patterns-and-implementation.md
## Real-World Impact
From debugging session (2025-10-03):
- Fixed 15 flaky tests across 3 files
- Pass rate: 60% → 100%
- Execution time: 40% faster
- No more race conditions
© bobmatnyc · MIT · bobmatnyc/claude-mpm-skills