Skip to content

adapter:create throws on duplicate rateLimit key instead of upserting #312

Description

@heyramzi

Bug

When rateLimit.storage is set to "database", the adapter:create mutation throws "rateLimit key already exists" on the second request to any auth endpoint within the same rate limit window.

This blocks all auth flows (session checks, OTP sending, sign-in) once a rate limit entry exists.

Reproduction

  1. Configure better-auth with rateLimit: { enabled: true, storage: "database" }
  2. Make any auth request (e.g., get-session)
  3. Make the same request again within the rate limit window
  4. The second request fails with:
[CONVEX M(adapter:create)] Uncaught Error: rateLimit key already exists
    at checkUniqueFields (adapter-utils.ts:276:2)
    at async handler (create-api.ts:86:8)

Root Cause

The better-auth core rate limiter (api/rate-limiter/index.mjs, createDatabaseStorageWrapper) has a set(key, value, _update) method:

  • When _update=false, it calls db.create() (insert)
  • When _update=true, it calls db.updateMany() (update)

Under certain conditions (race conditions, or when onResponseRateLimit runs with !data despite the key existing), _update is false even though a record with that key already exists. The Convex adapter's create handler runs checkUniqueFields, finds the duplicate key, and throws.

The error is caught and logged by better-auth core, but in the Convex adapter it surfaces as an uncaught mutation error that aborts the entire HTTP action.

Suggested Fix

The adapter's create handler for the rateLimit model could use upsert semantics instead of insert-or-throw. Since key is the unique field, the adapter could check for an existing entry and update it rather than throwing.

Alternatively, adding a try-catch around the checkUniqueFields call specifically for the rateLimit model would prevent the cascading failure.

Workaround

Set rateLimit: { enabled: false } or use storage: "memory" instead of "database".

Environment

  • @convex-dev/better-auth: 0.11.4
  • better-auth: latest (via better-auth/minimal)
  • Convex deployment: EU West 1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions