Skip to content

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.

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 of Promise.all()
  • Migration patterns - Converting try/catch code to Result types
  • Testing - Using unwrapOk/unwrapErr assertions

Copy the skill to your project’s .claude/skills directory:

Terminal window
# From your project root
mkdir -p .claude/skills
cp -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-patterns

Or Claude will use it contextually when you ask about:

  • Writing workflows
  • Migrating from try/catch
  • Debugging Result types
  • Using step helpers

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-run
await 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?).

// Correct - step handles errors automatically
const user = await step('getUser', () => deps.getUser(id));
const order = await step('createOrder', () => deps.createOrder(user));
// Wrong - never check result.ok inside workflows
const userResult = await deps.getUser(id);
if (!userResult.ok) return userResult; // Don't do this!
// Correct
const user = await step('getUser', () => deps.getUser(id));
const data = await step.try('fetch', () => fetch(url), { error: 'FETCH_ERROR' });
// Wrong - bare await bypasses error handling
const user = await deps.getUser(id); // Don't do this!
// Correct - allAsync preserves Result types
const [user, posts] = await step('fetchUserData', () => allAsync([
deps.getUser(id),
deps.getPosts(id),
]));
// Wrong - Promise.all loses type information
const [user, posts] = await Promise.all([...]); // Don't do this!

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 Results
async 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;
});
}

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, Claude may:

  • Suggest Promise.all() instead of allAsync()
  • 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.