К основному содержанию
скилл CX Codex

inngest-durable-functions — скилл для Claude Code: durable-функции

Скилл учит агента писать отказоустойчивые функции Inngest: шаги, мемоизация, ретраи, идемпотентность и отмена.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add inngest/inngest-skills --skill inngest-durable-functions -a claude-code
Открыть на GitHub

Что делает inngest-durable-functions

Скилл описывает модель durable execution в Inngest на TypeScript. Внутри — правила работы со step.run(), мемоизация завершённых шагов, сохранение состояния между сбоями инфраструктуры и автоматические ретраи с настраиваемым числом попыток. Есть разбор триггеров: события, cron и вызов другой функции через step.invoke().

Отдельные разделы — про идемпотентность на стороне продюсера и потребителя, отмену по событию через cancelOn и обработку ошибок. Автор советует заворачивать в шаги сетевые вызовы, работу с базой и файлами, а чистые вычисления и валидацию оставлять снаружи. Логирование тоже идёт вне шагов.

У функции есть жёсткие лимиты: 1000 шагов на запуск, 4 МБ данных на шаг и 32 МБ на всё состояние прогона. Каждый шаг — отдельный HTTP-запрос с накладными расходами. Если упираетесь в потолок, функцию разбивают на несколько и связывают через step.invoke() или step.sendEvent(). Материал рассчитан на TypeScript; для Python и Go автор отправляет к документации Inngest.

Какие задачи решает

  • сделать вебхук, который не теряет события при падении процесса
  • настроить cron-задачу с ретраями вместо хрупкого расписания
  • возобновить фоновую задачу с места сбоя, а не с начала
  • защититься от повторной отправки письма при дублирующемся событии
  • отменить заказ, если оплата не пришла в срок

Примеры запросов агенту

  • Напиши функцию Inngest на событие order/created, которая валидирует платёж, списывает деньги и шлёт письмо — каждый шаг в step.run()
  • Добавь в эту функцию cancelOn по событию order/cancelled с проверкой event.data.orderId == async.data.orderId
  • Сделай cron-триггер на каждый час с ретраями и идемпотентностью по event.data.cartId

Как установить inngest-durable-functions

1Через Skills CLI (Vercel)

npx skills add inngest/inngest-skills --skill inngest-durable-functions -a claude-code

2Вручную: положить файлы

~/.claude/skills/inngest-durable-functions/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/inngest-durable-functions/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о inngest-durable-functions

Как установить inngest-durable-functions в Claude?

Выполните npx skills add inngest/inngest-skills --skill inngest-durable-functions -a claude-code. Скилл появится в ~/.claude/skills/inngest-durable-functions/SKILL.md.

Что делает inngest-durable-functions?

Даёт агенту правила написания функций Inngest: триггеры, шаги и мемоизация, идемпотентность, отмена, ретраи и наблюдаемость.

На каком языке примеры в скилле?

На TypeScript. Для Python и Go автор отсылает к документации Inngest, хотя базовые концепции переносятся на любой язык.

Исходный текст

SKILL.md · Apache-2.0
---
name: inngest-durable-functions
description: Use when building functions that must survive process crashes, retry automatically on failure, run on a schedule, react to events, or maintain state across infrastructure failures — e.g., webhook handlers that drop events, flaky cron jobs, background jobs that fail mid-execution, or workflows that need to resume where they left off. Covers Inngest function configuration, triggers (events, cron, invoke), step execution and memoization, idempotency, cancellation, error handling, retries, logging, and observability.
---

# Inngest Durable Functions

Master Inngest's durable execution model for building fault-tolerant, long-running workflows. This skill covers the complete lifecycle from triggers to error handling.

> **These skills are focused on TypeScript.** For Python or Go, refer to the [Inngest documentation](https://www.inngest.com/llms.txt) for language-specific guidance. Core concepts apply across all languages.

## Core Concepts You Need to Know

### **Durable Execution Model**

- **Each step** should encapsulate side-effects and non-deterministic code
- **Memoization** prevents re-execution of completed steps
- **State persistence** survives infrastructure failures
- **Automatic retries** with configurable retry count

### **Step Execution Flow**

```typescript
// ❌ BAD: Non-deterministic logic outside steps
async ({ event, step }) => {
  const timestamp = Date.now(); // This runs multiple times!

  const result = await step.run("process-data", () => {
    return processData(event.data);
  });
};

// ✅ GOOD: All non-deterministic logic in steps
async ({ event, step }) => {
  const result = await step.run("process-with-timestamp", () => {
    const timestamp = Date.now(); // Only runs once
    return processData(event.data, timestamp);
  });
};
```

## Function Limits

**Every Inngest function has these hard limits:**

- **Maximum 1,000 steps** per function run
- **Maximum 4MB** returned data for each step
- **Maximum 32MB** combined function run state including, event data, step output, and function output
- Each step = separate HTTP request (~50-100ms overhead)

If you're hitting these limits, break your function into smaller functions connected via `step.invoke()` or `step.sendEvent()`.

## When to Use Steps

**Always wrap in `step.run()`:**

- API calls and network requests
- Database reads and writes
- File I/O operations
- Any non-deterministic operation
- Anything you want retried independently on failure

**Never wrap in `step.run()`:**

- Pure calculations and data transformations
- Simple validation logic
- Deterministic operations with no side effects
- Logging (use outside steps)

## Function Creation

### Basic Function Structure

```typescript
const processOrder = inngest.createFunction(
  {
    id: "process-order", // Unique, never change this
    triggers: [{ event: "order/created" }],
    retries: 4, // Default: 4 retries per step
    concurrency: 10 // Max concurrent executions
  },
  async ({ event, step }) => {
    // Your durable workflow
  }
);
```

### **Step IDs and Memoization**

```typescript
// Step IDs can be reused - Inngest handles counters automatically
const data = await step.run("fetch-data", () => fetchUserData());
const more = await step.run("fetch-data", () => fetchOrderData()); // Different execution

// Use descriptive IDs for clarity
await step.run("validate-payment", () => validatePayment(event.data.paymentId));
await step.run("charge-customer", () => chargeCustomer(event.data));
await step.run("send-confirmation", () => sendEmail(event.data.email));
```

## Triggers and Events

### **Event Triggers**

Triggers are defined in the `triggers` array in the first argument of `createFunction`:

```typescript
// Single event trigger
inngest.createFunction(
  { id: "my-fn", triggers: [{ event: "user/signup" }] },
  async ({ event }) => { /* ... */ }
);

// Event with conditional filter
inngest.createFunction(
  { id: "my-fn", triggers: [{ event: "user/action", if: 'event.data.action == "purchase" && event.data.amount > 100' }] },
  async ({ event }) => { /* ... */ }
);

// Multiple triggers (up to 10)
inngest.createFunction(
  {
    id: "my-fn",
    triggers: [
      { event: "user/signup" },
      { event: "user/login", if: 'event.data.firstLogin == true' },
      { cron: "0 9 * * *" } // Daily at 9 AM
    ]
  },
  async ({ event }) => { /* ... */ }
);
```

### **Cron Triggers**

```typescript
// Basic cron
inngest.createFunction(
  { id: "my-fn", triggers: [{ cron: "0 */6 * * *" }] }, // Every 6 hours
  async ({ step }) => { /* ... */ }
);

// With timezone
inngest.createFunction(
  { id: "my-fn", triggers: [{ cron: "TZ=Europe/Paris 0 12 * * 5" }] }, // Fridays at noon Paris time
  async ({ step }) => { /* ... */ }
);

// Combine with events
inngest.createFunction(
  {
    id: "my-fn",
    triggers: [
      { event: "manual/report.requested" },
      { cron: "0 0 * * 0" } // Weekly on Sunday
    ]
  },
  async ({ event, step }) => { /* ... */ }
);
```

### **Function Invocation**

```typescript
// Invoke another function as a step
const result = await step.invoke("generate-report", {
  function: generateReportFunction,
  data: { userId: event.data.userId }
});

// Use returned data
await step.run("process-report", () => {
  return processReport(result);
});
```

## Idempotency Strategies

### **Event-Level Idempotency (Producer Side)**

```typescript
// Prevent duplicate events with custom ID
await inngest.send({
  id: `checkout-completed-${cartId}`, // 24-hour deduplication
  name: "cart/checkout.completed",
  data: { cartId, email: "[email protected]" }
});
```

### **Function-Level Idempotency (Consumer Side)**

```typescript
const sendEmail = inngest.createFunction(
  {
    id: "send-checkout-email",
    triggers: [{ event: "cart/checkout.completed" }],
    // Only run once per cartId per 24 hours
    idempotency: "event.data.cartId"
  },
  async ({ event, step }) => {
    // This function won't run twice for same cartId
  }
);

// Complex idempotency keys
const processUserAction = inngest.createFunction(
  {
    id: "process-user-action",
    triggers: [{ event: "user/action.performed" }],
    // Unique per user + organization combination
    idempotency: 'event.data.userId + "-" + event.data.organizationId'
  },
  async ({ event, step }) => {
    /* ... */
  }
);
```

## Cancellation Patterns

### **Event-Based Cancellation**

In expressions, `event` = the **original** triggering event, `async` = the **new** event being matched. See [Expression Syntax Reference](../references/expressions.md) for full details.

```typescript
const processOrder = inngest.createFunction(
  {
    id: "process-order",
    triggers: [{ event: "order/created" }],
    cancelOn: [
      {
        event: "order/cancelled",
        if: "event.data.orderId == async.data.orderId"
      }
    ]
  },
  async ({ event, step }) => {
    await step.sleepUntil("wait-for-payment", event.data.paymentDue);
    // Will be cancelled if order/cancelled event received
    await step.run("charge-payment", () => processPayment(event.data));
  }
);
```

### **Timeout Cancellation**

```typescript
const processWithTimeout = inngest.createFunction(
  {
    id: "process-with-timeout",
    triggers: [{ event: "long/process.requested" }],
    timeouts: {
      start: "5m", // Cancel if not started within 5 minutes
      finish: "30m" // Cancel if not finished within 30 minutes
    }
  },
  async ({ event, step }) => {
    /* ... */
  }
);
```

### **Handling Cancellation Cleanup**

```typescript
// Listen for cancellation events
const cleanupCancelled = inngest.createFunction(
  { id: "cleanup-cancelled-process", triggers: [{ event: "inngest/function.cancelled" }] },
  async ({ event, step }) => {
    if (event.data.function_id === "process-order") {
      await step.run("cleanup-resources", () => {
        return cleanupOrderResources(event.data.run_id);
      });
    }
  }
);
```

## Error Handling and Retries

### **Default Retry Behavior**

- **5 total attempts** (1 initial + 4 retries) per step
- **Exponential backoff** with jitter
- **Independent retry counters** per step

### **Custom Retry Configuration**

```typescript
const reliableFunction = inngest.createFunction(
  {
    id: "reliable-function",
    triggers: [{ event: "critical/task" }],
    retries: 10 // Up to 10 retries per step
  },
  async ({ event, step, attempt }) => {
    // `attempt` is the function-level attempt counter (0-indexed)
    // It tracks retries for the currently executing step, not the overall function
    if (attempt > 5) {
      // Different logic for later attempts of the current step
    }
  }
);
```

### **Non-Retriable Errors**

Prevent retries for code that won't succeed upon retry.

```typescript
import { NonRetriableError } from "inngest";

const processUser = inngest.createFunction(
  { id: "process-user", triggers: [{ event: "user/process.requested" }] },
  async ({ event, step }) => {
    const user = await step.run("fetch-user", async () => {
      const user = await db.users.findOne(event.data.userId);

      if (!user) {
        // Don't retry - user doesn't exist
        throw new NonRetriableError("User not found, stopping execution");
      }

      return user;
    });

    // Continue processing...
  }
);
```

### **Custom Retry Timing**

```typescript
import { RetryAfterError } from "inngest";

const respectRateLimit = inngest.createFunction(
  { id: "api-call", triggers: [{ event: "api/call.requested" }] },
  async ({ event, step }) => {
    await step.run("call-api", async () => {
      const response = await externalAPI.call(event.data);

      if (response.status === 429) {
        // Retry after specific time from API
        const retryAfter = response.headers["retry-after"];
        throw new RetryAfterError("Rate limited", `${retryAfter}s`);
      }

      return response.data;
    });
  }
);
```

## Logging Best Practices

### **Proper Logging Setup**

```typescript
import winston from "winston";

// Configure logger
const logger = winston.createLogger({
  level: "info",
  format: winston.format.json(),
  transports: [new winston.transports.Console()]
});

const inngest = new Inngest({
  id: "my-app",
  logger // Pass logger to client
});

// Or use the built-in ConsoleLogger for simple log level control
import { ConsoleLogger, Inngest } from "inngest";

const inngest = new Inngest({
  id: "my-app",
  logger: new ConsoleLogger({ level: "debug" }) // "debug" | "info" | "warn" | "error"
});
```

**⚠️ v4 Breaking Change:** The `logLevel` option has been removed. Use the `logger` option with `ConsoleLogger` or a custom logger instead.

### **Function Logging Patterns**

```typescript
const processData = inngest.createFunction(
  { id: "process-data", triggers: [{ event: "data/process.requested" }] },
  async ({ event, step, logger }) => {
    // ✅ GOOD: Log inside steps to avoid duplicates
    const result = await step.run("fetch-data", async () => {
      logger.info("Fetching data for user", { userId: event.data.userId });
      return await fetchUserData(event.data.userId);
    });

    // ❌ AVOID: Logging outside steps can duplicate
    // logger.info("Processing complete"); // This could run multiple times!

    await step.run("log-completion", async () => {
      logger.info("Processing complete", { resultCount: result.length });
    });
  }
);
```

## Performance Optimization

### **Checkpointing**

Checkpointing is **enabled by default in v4**. It allows functions to persist state periodically during execution, reducing latency between steps.

```typescript
// Checkpointing is enabled by default in v4
// Configure maxRuntime for serverless platforms (set to 60-80% of platform timeout)
const realTimeFunction = inngest.createFunction(
  {
    id: "real-time-function",
    triggers: [{ event: "realtime/process" }],
    checkpointing: {
      maxRuntime: "50s", // For serverless with 60s timeout
    }
  },
  async ({ event, step }) => {
    // Steps execute immediately with periodic checkpointing
    const result1 = await step.run("step-1", () => process1(event.data));
    const result2 = await step.run("step-2", () => process2(result1));
    return { result2 };
  }
);

// Disable checkpointing if needed
const legacyFunction = inngest.createFunction(
  {
    id: "legacy-function",
    triggers: [{ event: "legacy/process" }],
    checkpointing: false
  },
  async ({ event, step }) => { /* ... */ }
);
```

## Advanced Patterns

### **Conditional Step Execution**

```typescript
const conditionalProcess = inngest.createFunction(
  { id: "conditional-process", triggers: [{ event: "process/conditional" }] },
  async ({ event, step }) => {
    const userData = await step.run("fetch-user", () => {
      return getUserData(event.data.userId);
    });

    // Conditional step execution
    if (userData.isPremium) {
      await step.run("premium-processing", () => {
        return processPremiumFeatures(userData);
      });
    }

    // Always runs
    await step.run("standard-processing", () => {
      return processStandardFeatures(userData);
    });
  }
);
```

### **Error Recovery Patterns**

```typescript
const robustProcess = inngest.createFunction(
  { id: "robust-process", triggers: [{ event: "process/robust" }] },
  async ({ event, step }) => {
    let primaryResult;

    try {
      primaryResult = await step.run("primary-service", () => {
        return callPrimaryService(event.data);
      });
    } catch (error) {
      // Fallback to secondary service
      primaryResult = await step.run("fallback-service", () => {
        return callSecondaryService(event.data);
      });
    }

    return { result: primaryResult };
  }
);
```

## Common Mistakes to Avoid

1. **❌ Non-deterministic code outside steps**
2. **❌ Database calls outside steps**
3. **❌ Logging outside steps (causes duplicates)**
4. **❌ Changing step IDs after deployment**
5. **❌ Not handling NonRetriableError cases**
6. **❌ Ignoring idempotency for critical functions**

## Next Steps

- See **inngest-steps** for detailed step method reference
- See [references/step-execution.md](references/step-execution.md) for detailed step patterns
- See [references/error-handling.md](references/error-handling.md) for comprehensive error strategies
- See [references/observability.md](references/observability.md) for monitoring and tracing setup
- See [references/checkpointing.md](references/checkpointing.md) for performance optimization details

---

_This skill covers Inngest's durable function patterns. For event sending and webhook handling, see the `inngest-events` skill._

© inngest · Apache-2.0 · inngest/inngest-skills

Похожие скиллы

Всё по теме «Бэкенд и API» →
inngest sickn33 Скилл для работы с Inngest: настраивай фоновые задачи, event-driven workflows и долгоживущие процессы без управления очередями и воркерами. Пригодится, если нужны отложенные операции, многошаговые workflow или обработка вебхуков. CX Codex +1 1 200установок aws-lambda-durable-functions aws Скилл учит ИИ писать долгоживущие функции AWS Lambda с сохранением состояния до одного года. Пригодится при сборке серверлесс-пайплайнов и пошаговых сценариев для ИИ-агентов. CX Codex +1 5 800установок trigger-tasks triggerdotdev Помогает писать фоновые задачи для Trigger.dev через @trigger.dev/sdk. Пригодится при настройке повторов, очередей, cron-расписаний и валидации схем в каталоге /trigger. CX Codex +1 3 200установок netlify-functions netlify Помогает агенту писать и настраивать серверless-функции Netlify на TypeScript, JavaScript или Go: маршруты, cron, лимиты, чтение секретов. CX Codex +1 3 200установок trigger-setup triggerdotdev Помогает настроить Trigger.dev в проекте с нуля: создаёт конфигурационные файлы, подключает пакеты и подготавливает первую фоновую задачу. CX Codex +1 2 800установок workflow-automation sickn33 Скилл про долговечное выполнение шагов: если сеть отвалилась посередине, workflow продолжит с места обрыва, а не с начала. CX Codex +1 1 500установок

Из того же набора10

inngest-steps Учит агента проектировать надёжные шаги в воркфлоу Inngest на TypeScript. Пригодится при настройке задержек, ожидании вебхуков и фоновых задач. CX Codex +1 3 800установок inngest-events Помогает проектировать событийные воркфлоу Inngest: имена событий, идемпотентность по id, fan-out и системные события. CX Codex +1 3 800установок inngest-setup Скилл ставит Inngest в TypeScript-проект: клиент, переменные окружения, serve-эндпоинты и локальный dev-сервер. Пригодится, когда нужны фоновые задачи и вебхуки, переживающие падение процесса. CX Codex +1 3 700установок inngest-middleware Скилл учит ИИ настраивать middleware для Inngest v4: внедрять зависимости в обработчики и шифровать полезную нагрузку функций. CX Codex +1 3 600установок inngest-flow-control Скилл по flow control в Inngest: concurrency, throttling, rate limiting, debounce, priority, singleton и батчинг событий. Пригодится, когда внешние API отвечают 429. CX Codex +1 1 600установок inngest-realtime Скилл учит агента публиковать обновления из durable-функций Inngest в живой интерфейс: статусы заказов, стриминг токенов, логи и подтверждения от пользователя. CX Codex +1 797установок inngest-api-cli Скилл учит агента работать с ресурсами Inngest через npx inngest-cli@latest api: разбирать запуски, искать события, синхронизировать приложения. CX Codex +1 776установок inngest-agents Скилл для сборки и отладки агентов на Inngest и AgentKit: вызовы моделей, инструменты, ожидание одобрения человека, прогресс в реальном времени. CX Codex +1 657установок

Спонсоры проекта

Компании, которые помогают держать аналитику открытой