From 0bcd7d5fb5f20864653e8f6986c4d83c1fa415dd Mon Sep 17 00:00:00 2001 From: Taesu Date: Wed, 4 Feb 2026 22:27:32 +0900 Subject: [PATCH] fix(db): skip 'adapter.delete' in deleteWithHooks when entity not found --- .../src/db/internal-adapter.test.ts | 27 +++++++++++++++++++ packages/better-auth/src/db/with-hooks.ts | 4 ++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/better-auth/src/db/internal-adapter.test.ts b/packages/better-auth/src/db/internal-adapter.test.ts index 317de38d23..48072d1b94 100644 --- a/packages/better-auth/src/db/internal-adapter.test.ts +++ b/packages/better-auth/src/db/internal-adapter.test.ts @@ -258,6 +258,33 @@ describe("internal adapter test", async () => { expect(hookVerificationDeleteAfter).toHaveBeenCalledOnce(); }); + it("should not call adapter.delete for missing verification record (prevents Prisma P2025)", async () => { + const verification = await internalAdapter.createVerificationValue({ + identifier: "missing-entity-test", + value: "test-value", + expiresAt: new Date(Date.now() + 60000), + }); + + // Remove the DB record so the entity no longer exists + await authContext.adapter.deleteMany({ + model: "verification", + where: [{ field: "identifier", value: verification.identifier }], + }); + + const deleteSpy = vi.spyOn(authContext.adapter, "delete"); + + await internalAdapter.deleteVerificationByIdentifier("missing-entity-test"); + + // adapter.delete should NOT have been called because + // deleteWithHooks skips deletion when the entity is not found + const verificationDeleteCalls = deleteSpy.mock.calls.filter( + (call) => call[0].model === "verification", + ); + expect(verificationDeleteCalls.length).toBe(0); + + deleteSpy.mockRestore(); + }); + describe("verification token storage", () => { it("should hash identifier when storeIdentifier is 'hashed'", async () => { const hashedOpts = { diff --git a/packages/better-auth/src/db/with-hooks.ts b/packages/better-auth/src/db/with-hooks.ts index 5b92a79e68..63aa9dbebe 100644 --- a/packages/better-auth/src/db/with-hooks.ts +++ b/packages/better-auth/src/db/with-hooks.ts @@ -226,8 +226,10 @@ export function getWithHooks( ? await customDeleteFn.fn(where) : null; + const shouldRunAdapterDelete = + !customDeleteFn || customDeleteFn.executeMainFn; const deleted = - !customDeleteFn || customDeleteFn.executeMainFn + shouldRunAdapterDelete && entityToDelete ? await (await getCurrentAdapter(adapter)).delete({ model, where,