From harness-claude
Handles duplicate message delivery safely using idempotency keys and deduplication stores. Useful for at-least-once message delivery systems, payment processing, and retry logic.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:events-idempotencyThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Handle duplicate message delivery safely using idempotency keys and deduplication stores.
Handle duplicate message delivery safely using idempotency keys and deduplication stores.
Idempotency key pattern for API endpoints:
import { Redis } from 'ioredis';
const redis = new Redis({ host: 'localhost', port: 6379 });
interface IdempotentResult<T> {
data: T;
fromCache: boolean;
}
async function withIdempotency<T>(
idempotencyKey: string,
ttlSeconds: number,
operation: () => Promise<T>
): Promise<IdempotentResult<T>> {
const cacheKey = `idempotency:${idempotencyKey}`;
// Check if result already exists
const cached = await redis.get(cacheKey);
if (cached) {
return { data: JSON.parse(cached) as T, fromCache: true };
}
// Lock to prevent concurrent duplicate processing
const lockKey = `lock:${idempotencyKey}`;
const locked = await redis.set(lockKey, '1', 'EX', 30, 'NX');
if (!locked) {
// Another instance is processing — wait and retry
await new Promise((r) => setTimeout(r, 500));
return withIdempotency(idempotencyKey, ttlSeconds, operation);
}
try {
const result = await operation();
await redis.set(cacheKey, JSON.stringify(result), 'EX', ttlSeconds);
return { data: result, fromCache: false };
} finally {
await redis.del(lockKey);
}
}
// HTTP handler with idempotency key header
async function createPaymentHandler(req: Request, res: Response): Promise<void> {
const idempotencyKey = req.headers['idempotency-key'] as string;
if (!idempotencyKey) {
res.status(400).json({ error: 'Idempotency-Key header required' });
return;
}
const { data, fromCache } = await withIdempotency(
idempotencyKey,
86_400, // 24 hours
() => processPayment(req.body)
);
res
.status(fromCache ? 200 : 201)
.set('Idempotency-Replayed', fromCache ? 'true' : 'false')
.json(data);
}
Database-level deduplication (for message consumers):
// Store processed event IDs in a dedupe table
async function processEventIdempotent(
eventId: string,
process: () => Promise<void>
): Promise<void> {
// Attempt to insert the event ID — unique constraint prevents duplicates
const inserted = await db.query<{ inserted: boolean }>(
`INSERT INTO processed_events (event_id, processed_at)
VALUES ($1, NOW())
ON CONFLICT (event_id) DO NOTHING
RETURNING true as inserted`,
[eventId]
);
if (!inserted.rows[0]?.inserted) {
console.log(`Event ${eventId} already processed — skipping`);
return; // idempotent — safe to skip
}
await process();
}
// SQL schema
/*
CREATE TABLE processed_events (
event_id TEXT PRIMARY KEY,
processed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Cleanup old deduplication records (run periodically)
DELETE FROM processed_events WHERE processed_at < NOW() - INTERVAL '30 days';
*/
Kafka consumer with deduplication:
consumer.run({
eachMessage: async ({ message, topic, partition }) => {
const eventId =
message.headers?.['event-id']?.toString() ?? `${topic}:${partition}:${message.offset}`;
await processEventIdempotent(eventId, async () => {
const event = JSON.parse(message.value!.toString());
await handleEvent(event);
});
},
});
Idempotent HTTP operation design:
// Design operations to be safe to retry:
// GOOD: upsert instead of insert
await db.user.upsert({
where: { email: data.email },
update: {}, // no-op if exists
create: { email: data.email, name: data.name },
});
// GOOD: check-then-act with unique constraint
try {
await db.subscription.create({ data: { userId, planId } });
} catch (err) {
if (isUniqueConstraintViolation(err)) {
return db.subscription.findUnique({ where: { userId_planId: { userId, planId } } });
}
throw err;
}
// BAD: plain insert that fails on duplicate
await db.subscription.create({ data: { userId, planId } }); // throws on retry
Idempotency window: The deduplication record must live long enough to catch late retries. 24 hours is common for API keys. 30 days is common for event consumers. Match to your retry window.
Exactly-once vs. at-least-once + idempotency: True exactly-once delivery is extremely hard in distributed systems. The practical solution: at-least-once delivery + idempotent consumers = effectively exactly-once behavior.
Anti-patterns:
Stripe's approach: Idempotency keys are sent in request headers. Stripe stores the response for 24h and returns the same response for duplicate keys. They also lock to prevent concurrent identical requests.
microservices.io/patterns/communication-style/idempotent-consumer.html
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeImplements idempotency for API endpoints and message consumers to safely handle retries without duplicate side effects.
Provides patterns for designing idempotent APIs with keys to handle retries safely, prevent duplicates, and ensure at-most-once semantics in payments/orders.
Implements idempotent API operations with keys, Redis response caching, and DB constraints for safe retries in payments, webhooks, or duplicate processing.