Claude Code Skill
The awaitly-patterns skill teaches Claude Code the correct patterns for writing awaitly workflows. When enabled, Claude understands the rules, helpers, and best practices.
What it provides
Section titled “What it provides”The skill teaches Claude:
- step() forms - When to use direct vs thunk form
- Error handling - How step() handles early exit, no manual checking
- Step helpers -
step.try(),step.retry(),step.withTimeout() - Concurrency - Using
allAsync()instead ofPromise.all() - Migration patterns - Converting try/catch code to Result types
- Testing - Using
unwrapOk/unwrapErrassertions
Installation
Section titled “Installation”Copy the skill to your project’s .claude/skills directory:
# From your project rootmkdir -p .claude/skillscp -r node_modules/awaitly/.claude/skills/awaitly-patterns .claude/skills/Or create the structure manually:
your-project/├── .claude/│ └── skills/│ └── awaitly-patterns/│ └── SKILL.md└── ...Once installed, Claude Code automatically loads skills from your project’s .claude/skills directory. You can invoke it explicitly:
/awaitly-patternsOr Claude will use it contextually when you ask about:
- Writing workflows
- Migrating from try/catch
- Debugging Result types
- Using step helpers
Key rules Claude learns
Section titled “Key rules Claude learns”R1: step() requires a string ID; supports direct and thunk forms
Section titled “R1: step() requires a string ID; supports direct and thunk forms”step() requires a string ID as the first argument: step('id', fnOrResult, opts?).
// Thunk form - deferred, enables retry/caching (preferred when using cache/retry)await step('getUser', () => deps.getUser(id));
// Direct form - already started, can't retry/cache-before-runawait step('getUser', deps.getUser(id));Rule: If an operation must be re-executed (retry) or conditionally executed (cache), use the thunk form. Every step type takes an ID as the first argument: step(id, fn, opts), step.retry(id, operation, options), step.withTimeout(id, operation, options), step.try(id, fn, opts), step.fromResult(id, fn, opts), step.sleep(id, duration, opts?).
R2: step() handles early exit
Section titled “R2: step() handles early exit”// Correct - step handles errors automaticallyconst user = await step('getUser', () => deps.getUser(id));const order = await step('createOrder', () => deps.createOrder(user));
// Wrong - never check result.ok inside workflowsconst userResult = await deps.getUser(id);if (!userResult.ok) return userResult; // Don't do this!R3: All async work through step()
Section titled “R3: All async work through step()”// Correctconst user = await step('getUser', () => deps.getUser(id));const data = await step.try('fetch', () => fetch(url), { error: 'FETCH_ERROR' });
// Wrong - bare await bypasses error handlingconst user = await deps.getUser(id); // Don't do this!R4: No Promise.all()
Section titled “R4: No Promise.all()”// Correct - allAsync preserves Result typesconst [user, posts] = await step('fetchUserData', () => allAsync([ deps.getUser(id), deps.getPosts(id),]));
// Wrong - Promise.all loses type informationconst [user, posts] = await Promise.all([...]); // Don't do this!Example conversation
Section titled “Example conversation”You: Help me migrate this function to awaitly:
async function getOrder(id: string) { try { const user = await fetchUser(id); const order = await createOrder(user); return order; } catch (e) { throw new Error('Order failed'); }}Claude (with skill): I’ll migrate this to awaitly patterns…
import { ok, err, type AsyncResult } from 'awaitly';import { run } from 'awaitly/run';
// 1. Convert deps to return Resultsasync function fetchUser(id: string): AsyncResult<User, 'NOT_FOUND'> { const user = await db.find(id); return user ? ok(user) : err('NOT_FOUND');}
async function createOrder(user: User): AsyncResult<Order, 'ORDER_FAILED'> { // ...}
// 2. Use run() with step()async function getOrder(id: string) { return run(async ({ step }) => { const user = await step('fetchUser', () => fetchUser(id)); const order = await step('createOrder', () => createOrder(user)); return order; });}Customizing the skill
Section titled “Customizing the skill”You can edit .claude/skills/awaitly-patterns/SKILL.md to:
- Add your project-specific conventions
- Include examples from your codebase
- Emphasize patterns your team uses most
Without the skill
Section titled “Without the skill”Without the skill, Claude may:
- Suggest
Promise.all()instead ofallAsync() - Forget to use thunks for retry/caching
- Add manual
if (!result.ok)checks inside workflows - Mix throwing and Result patterns inconsistently
The skill ensures Claude gives you correct awaitly code from the start.