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

idempotency-handling — скилл для Claude Code: установка и суть

Скилл помогает настроить идемпотентные операции в API через Redis и базу данных. Защищает платежи и вебхуки от повторной обработки при сбоях сети.

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

Что делает idempotency-handling

Скилл содержит архитектурные шаблоны для повторно вызываемых API. Внутри собраны готовые примеры middleware для Node.js с кешированием ответов в Redis на 24 часа и SQL-схема таблицы idempotency_keys со статусами запросов.

Принцип защиты строится на сверке хэша запроса request_hash и атомарной вставке через ON CONFLICT (key) DO NOTHING. Если повторный вызов пришёл во время обработки первого, база отклонит дубль из-за уникального ключа. Это исключает повторные списания. Без Redis или базы данных такая защита не сработает.

Для предотвращения разрастания таблицы скилл предлагает очистку устаревших ключей порциями по 1000 записей. Примеры написаны для pg_cron, фоновых задач в Node.js и скриптов на Python.

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

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

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

  • Спроектируй Express middleware для проверки idempotency-key через Redis с временем жизни 24 часа
  • Создай таблицу idempotency_keys в PostgreSQL и напиши обработчик платежа с проверкой хэша запроса
  • Напиши периодический фоновый скрипт на Python для удаления ключей с истёкшим сроком годности

Как установить idempotency-handling

1Через Skills CLI (Vercel)

npx skills add secondsky/claude-skills --skill idempotency-handling -a claude-code

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

~/.claude/skills/idempotency-handling/SKILL.md

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

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

Вопросы о idempotency-handling

Как установить idempotency-handling в Claude Code?

Выполните команду npx skills add secondsky/claude-skills --skill idempotency-handling -a claude-code. Скилл сразу станет доступен агенту.

Для каких задач пригодится idempotency-handling?

Он нужен при обработке платежей, оформлении заказов, отправке писем и приёме вебхуков. Шаблоны из скилла не дают продублировать операцию при сбоях сети.

Как этот скилл решает проблему параллельных запросов?

Через атомарную вставку в базу со статусом processing и конструкцией ON CONFLICT DO NOTHING. Первый запрос забирает обработку на себя, а параллельный поток получает отказ.

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

SKILL.md · MIT
---
name: idempotency-handling
description: Idempotent API operations with idempotency keys, Redis caching, DB constraints. Use for payment systems, webhook retries, safe retries, or encountering duplicate processing, race conditions, key expiry errors.
license: MIT
---

# Idempotency Handling

Ensure operations produce identical results regardless of execution count.

## Idempotency Key Pattern

```javascript
const redis = require('redis');
const client = redis.createClient();

async function idempotencyMiddleware(req, res, next) {
  const key = req.headers['idempotency-key'];
  if (!key) return next();

  const cached = await client.get(`idempotency:${key}`);
  if (cached) {
    const { status, body } = JSON.parse(cached);
    return res.status(status).json(body);
  }

  // Store original send
  const originalSend = res.json.bind(res);
  res.json = async (body) => {
    await client.setEx(
      `idempotency:${key}`,
      86400, // 24 hours
      JSON.stringify({ status: res.statusCode, body })
    );
    return originalSend(body);
  };

  next();
}
```

## Database-Backed Idempotency

```sql
CREATE TABLE idempotency_keys (
  key VARCHAR(255) PRIMARY KEY,
  request_hash VARCHAR(64) NOT NULL,
  response JSONB,
  status VARCHAR(20) DEFAULT 'processing',
  created_at TIMESTAMP DEFAULT NOW(),
  expires_at TIMESTAMP DEFAULT NOW() + INTERVAL '24 hours'
);

CREATE INDEX idx_idempotency_expires ON idempotency_keys(expires_at);
```

```javascript
async function processPayment(idempotencyKey, payload) {
  const requestHash = crypto.createHash('sha256')
    .update(JSON.stringify(payload)).digest('hex');

  // Try to insert with 'processing' status - only one request will succeed
  const insertResult = await db.query(
    `INSERT INTO idempotency_keys (key, request_hash, status)
     VALUES ($1, $2, 'processing')
     ON CONFLICT (key) DO NOTHING
     RETURNING *`,
    [idempotencyKey, requestHash]
  );

  // If we inserted the row (rowCount === 1), we're responsible for processing
  if (insertResult.rowCount === 1) {
    try {
      // Execute the payment
      const result = await executePayment(payload);

      // Update to completed with response
      await db.query(
        'UPDATE idempotency_keys SET status = $1, response = $2 WHERE key = $3',
        ['completed', JSON.stringify(result), idempotencyKey]
      );

      return result;
    } catch (error) {
      // Mark as failed on error
      await db.query(
        'UPDATE idempotency_keys SET status = $1, response = $2 WHERE key = $3',
        ['failed', JSON.stringify({ error: error.message }), idempotencyKey]
      );
      throw error;
    }
  }

  // Another request is/was processing this key - check status
  const existing = await db.query(
    'SELECT * FROM idempotency_keys WHERE key = $1',
    [idempotencyKey]
  );

  const row = existing.rows[0];
  if (!row) {
    throw new Error('Unexpected: idempotency key vanished');
  }

  // Verify request hasn't changed
  if (row.request_hash !== requestHash) {
    throw new Error('Idempotency key reused with different request');
  }

  // Check status
  if (row.status === 'completed') {
    return JSON.parse(row.response);
  } else if (row.status === 'processing') {
    throw new Error('Request already processing - retry later');
  } else if (row.status === 'failed') {
    const failedResponse = JSON.parse(row.response);
    throw new Error(`Previous attempt failed: ${failedResponse.error}`);
  }

  throw new Error(`Unknown status: ${row.status}`);
}
```

## When to Apply

- Payment processing
- Order creation
- Webhook handling
- Email sending
- Any operation where duplicates cause issues

## Best Practices

- Require idempotency keys for mutations
- Validate request body matches stored request
- Set appropriate TTL (24 hours typical)
- Use atomic database operations
- Implement cleanup jobs to prevent table bloat

### TTL Cleanup Strategy

To prevent unbounded table growth, implement periodic cleanup of expired keys:

**Option 1: Scheduled Database Job (PostgreSQL)**
```sql
-- Run hourly via pg_cron or external scheduler
DELETE FROM idempotency_keys
WHERE expires_at < NOW()
LIMIT 1000; -- Batch delete to avoid long locks
```

**Option 2: Application Cleanup Job (Node.js)**
```javascript
// Run via cron or job scheduler (e.g., node-cron, Bull)
async function cleanupExpiredKeys() {
  try {
    const result = await db.query(
      'DELETE FROM idempotency_keys WHERE expires_at < NOW()'
    );
    console.log(`Cleaned up ${result.rowCount} expired idempotency keys`);
  } catch (error) {
    console.error('Cleanup job failed:', error);
  }
}

// Schedule to run every hour
cron.schedule('0 * * * *', cleanupExpiredKeys);
```

**Option 3: Application Cleanup Job (Python)**
```python
import asyncio
from datetime import datetime

async def cleanup_expired_keys():
    """Remove expired idempotency keys to prevent table bloat."""
    try:
        result = await db.execute(
            "DELETE FROM idempotency_keys WHERE expires_at < $1",
            datetime.now()
        )
        print(f"Cleaned up {result} expired idempotency keys")
    except Exception as e:
        print(f"Cleanup job failed: {e}")

# Run with APScheduler, Celery, or similar
# scheduler.add_job(cleanup_expired_keys, 'interval', hours=1)
```

**Cleanup Best Practices:**
- Run cleanup during low-traffic periods to minimize lock contention
- Use batched deletes (`LIMIT 1000`) for large tables
- Monitor cleanup job execution and failures
- Consider partitioning the table by created_at for easier cleanup
- Set up alerts if table size grows unexpectedly

© secondsky · MIT · secondsky/claude-skills

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

Все скиллы по теме «Бэкенд и API» →
payment-idempotency vtex Скилл для кода платёжного коннектора: не даёт повторным запросам Gateway создать второе списание у эквайера. CX Codex +1 1 100установок api-idempotency-retry Samuel Berthe Помогает спроектировать Idempotency-Key и правила повторов для публичного API, чтобы интеграторы ретраили POST без дублей. CX Codex +1 8 000установок crontap-retries-and-idempotency Crontap Настраивает политику повторов в Crontap и помогает сделать эндпоинт идемпотентным, чтобы повторный запуск не создавал дублей. CX Codex +1 881установок backend-development MoizIbnYousaf Скилл для проектирования REST API, схем баз данных и микросервисной архитектуры. Пригодится, когда агент пишет бэкенд и нужно, чтобы он держался одних конвенций. CX Codex +1 868установок backend-patterns affaan-m Скилл с паттернами бэкенда для Node.js, Express и Next.js API routes: слои, кэш, транзакции, обработка ошибок. CX Codex +1 6 000установок iii-error-handling iii-hq Скилл учит агента распознавать коды ошибок iii-движка и SDK, различать сетевые сбои от отказов в доступе, определять, когда повторять вызов безопасно. Нужен, если пишешь код на Node, Python, Rust или в браузере с вызовами удалённых функций. CX Codex +1 4 100установок

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

bun-test-coverage Скилл для работы с покрытием тестов в Bun: флаг --coverage, отчёты lcov, пороги и запуск в CI. Пригодится, когда тесты уже есть, а процент покрытия надо видеть и держать. CX Codex +1 403установок bun-workers Скилл подсказывает агенту, как запускать Web Workers и worker_threads в Bun: параллельные задачи, пул воркеров, обмен сообщениями. CX Codex +1 402установок dependency-upgrade Помогает обновлять зависимости без риска: настраивает карантин для новых версий, блокирует postinstall-скрипты и ведёт обновления поэтапно. CX Codex +1 404установок api-changelog-versioning Генерирует чёткие журналы изменений API, графики устаревания версий и пошаговые руководства по миграции для клиентов сервиса. CX Codex +1 402установок nuxt-server Скилл для серверной разработки на Nuxt 4 с Nitro: API-роуты, middleware, базы данных и WebSocket. Пригодится, когда бэкенд живёт внутри Nuxt-проекта. CX Codex +1 401установок bun-bundler Скилл обучает агента собирать проекты на JavaScript и TypeScript через Bun. Пригодится при настройке CLI-сборки или переходе на API Bun.build. CX Codex +1 400установок bun-http-server Скилл подсказывает агенту, как поднять HTTP-сервер через Bun.serve(): маршруты, разбор тела запроса, ответы, CORS. CX Codex +1 400установок bun-cloudflare-workers Скилл для сборки и деплоя Cloudflare Workers на Bun: подсказывает структуру проекта, биндинги KV, D1, R2 и Durable Objects. CX Codex +1 402установок

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

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