Skip to content

feat(PAB-21): Idempotency-Key on chat completions + request_dedup table#97

Merged
pablopunk merged 2 commits into
mainfrom
agent/coder/fe017c2f
Jul 12, 2026
Merged

feat(PAB-21): Idempotency-Key on chat completions + request_dedup table#97
pablopunk merged 2 commits into
mainfrom
agent/coder/fe017c2f

Conversation

@pablopunk

@pablopunk pablopunk commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

This PR implements idempotency support for the chat completions API endpoint, allowing clients to safely retry requests without risk of duplicate processing or billing.

Key Changes

Idempotency-Key Header Support:

  • The /api/v1/chat/completions endpoint now reads an Idempotency-Key header from incoming requests and passes it through to the proxy pipeline.

Request Deduplication Table:

  • A new request_dedup table stores deduplication state per user and idempotency key, with a unique index on (user_id, idempotency_key). It tracks request status (in_flight, completed, failed), cached response data, headers, and timestamps.

Deduplication Logic (handleDedup):

  • First request with a key: Inserts a new in_flight row and proceeds normally with the upstream call.
  • Duplicate while in-flight (non-stale): Returns a 409 Conflict with idempotency_conflict error, indicating the request is already being processed.
  • Stale in-flight row (>5 minutes old): Reclaims the row (resets to in_flight) and proceeds with the request.
  • Completed non-streaming response: Replays the cached response (status, body, headers) with the same deterministic request ID, so the client receives an identical result.
  • Completed streaming response: Returns 409 since streaming responses cannot be replayed.
  • Failed previous attempt: Reclaims the row and retries the request.

Deterministic Request IDs:

  • When dedup is enabled, the request ID is derived from a SHA-256 hash of user_id:idempotency_key, ensuring replayed responses carry the same x-request-id.

Post-Response Persistence:

  • After a non-streaming upstream response completes, the dedup row is updated to completed with the response JSON, headers, and upstream status.
  • For streaming responses, the row is marked completed but no response body is stored (replay not supported).

Kill Switch:

  • The entire dedup feature can be disabled via the idempotency_dedup backend kill switch.

Testing:

  • Unit tests cover all dedup states (new, in-flight, stale, completed non-streaming, completed streaming, failed).
  • A smoke test script (smoke-idempotency.sh) sends two identical requests with the same Idempotency-Key and asserts both return 200 with the same x-request-id.

Add request_dedup table with uniqueIndex(userId, idempotencyKey) for
idempotent chat completion retries. Generate stable requestId via hash
of userId:idempotencyKey, preventing double-charge via credit_ledger's
existing uniqueIndex on (userId, reason, refId).

- HandleDedup: INSERT ON CONFLICT DO NOTHING; replay completed non-streaming;
  return 409 for streaming/in-flight; reclaim stale in_flight rows (>5min)
- Gate behind NEVERMIND_KILL_SWITCHES 'idempotency_dedup' kill switch
- 6 new unit tests + smoke-idempotency.sh integration script
- Credit ledger insert uses onConflictDoNothing as safety net

Ref: PAB-21
Co-authored-by: multica-agent <github@multica.ai>
@vercel

vercel Bot commented Jul 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nvm Ready Ready Preview, Comment Jul 12, 2026 12:39pm

@kody-ai

kody-ai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

Comment thread backend/src/lib/proxy.ts
Comment on lines +210 to +224
if (existing.status === 'in_flight') {
const createdAt = new Date(existing.createdAt).getTime();
if (Date.now() - createdAt > DEDUP_STALE_MS) {
await db.update(requestDedup).set({
status: 'in_flight',
requestId,
createdAt: new Date(),
requestHash: null,
responseJson: null,
responseHeaders: null,
upstreamStatus: null,
completedAt: null,
}).where(eq(requestDedup.id, existing.id));
return undefined;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug critical

Race condition in backend/src/lib/proxy.ts:212-223 allows two stale retries to reclaim the same requestDedup row because the UPDATE only filters on id, so both requests can pass Date.now() - createdAt > DEDUP_STALE_MS, call the upstream, and recordUsage. Guard the reclaim with status = 'in_flight' and fail the second winner with a 409 idempotency_conflict to preserve the dedup guarantee.

if (Date.now() - createdAt > DEDUP_STALE_MS) {
  const [reclaimed] = await db.update(requestDedup).set({
    status: 'in_flight',
    requestId,
    createdAt: new Date(),
    requestHash: null, responseJson: null, responseHeaders: null, upstreamStatus: null, completedAt: null,
  }).where(and(
    eq(requestDedup.id, existing.id),
    eq(requestDedup.status, 'in_flight'),
  )).returning({ id: requestDedup.id });
  if (!reclaimed) return withRequestId(Response.json({ error: { type: 'idempotency_conflict', message: 'Request already in progress' } }, { status: 409 }), requestId);
  return undefined;
}
Prompt for LLM

File backend/src/lib/proxy.ts:

Line 210 to 224:

Race condition in backend/src/lib/proxy.ts:212-223 allows two stale retries to reclaim the same `requestDedup` row because the `UPDATE` only filters on `id`, so both requests can pass `Date.now() - createdAt > DEDUP_STALE_MS`, call the upstream, and recordUsage. Guard the reclaim with `status = 'in_flight'` and fail the second winner with a 409 `idempotency_conflict` to preserve the dedup guarantee.

Suggested Code:

    if (Date.now() - createdAt > DEDUP_STALE_MS) {
      const [reclaimed] = await db.update(requestDedup).set({
        status: 'in_flight',
        requestId,
        createdAt: new Date(),
        requestHash: null, responseJson: null, responseHeaders: null, upstreamStatus: null, completedAt: null,
      }).where(and(
        eq(requestDedup.id, existing.id),
        eq(requestDedup.status, 'in_flight'),
      )).returning({ id: requestDedup.id });
      if (!reclaimed) return withRequestId(Response.json({ error: { type: 'idempotency_conflict', message: 'Request already in progress' } }, { status: 409 }), requestId);
      return undefined;
    }

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@pablopunk
pablopunk merged commit efb275e into main Jul 12, 2026
5 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant