fix(run-store): fix batch idempotency lookup on the dedicated run-ops store (#4271)

## Summary

`batchTrigger` requests that set a per-item `idempotencyKey` failed with
a 500 when the run-store is split across databases: the per-item
idempotency lookup errored before any run was created. Batches without
per-item keys, single `trigger` idempotency, and batch-level
(`idempotency-key` header) idempotency were unaffected.

## Root cause

`findRunsByIdempotencyKeys` built its `UNION ALL` of per-key
point-lookups with `@trigger.dev/database`'s `Prisma.sql` /
`Prisma.join`, then executed it on whichever store client it was handed.
On the dedicated run-ops store that client is a *separate* generated
Prisma client, and a `Sql` object from a different generated client is
not recognized: the bare `$queryRaw(Prisma.join(...))` form dropped the
query text entirely (`Argument \`query\` is missing`). The
tagged-template form is no better here: joining nested `Prisma.sql`
fragments across the two clients mis-numbers the bound parameters
(`syntax error at or near "$1"`).

## Fix

Build the lookup as a plain parameterized string and run it via
`$queryRawUnsafe` with positional placeholders and bound values, so it
no longer depends on which generated client executes it. The query text
contains only static SQL and integer placeholders; every value
(`runtimeEnvironmentId`, `taskIdentifier`, each key) is bound, so it is
not a raw-interpolation site. Same per-key point-lookup shape as before,
no change on the single-client path.

Verified end-to-end against a bundled build with the run-store split
enabled: before the fix, `batchTrigger` with a per-item key 500s; after,
it returns the runs and dedups correctly across fresh, repeat, and mixed
batches.
This commit is contained in:
Eric Allam
2026-07-15 19:36:12 +01:00
committed by GitHub
parent 80cbc46bf6
commit 43250522a5
2 changed files with 16 additions and 4 deletions
@@ -0,0 +1,6 @@
---
area: webapp
type: fix
---
Fix batchTrigger requests that set a per-item idempotency key failing with an error instead of creating and deduplicating the runs
@@ -72,6 +72,7 @@ export interface RunOpsCapableClient {
// Standalone entity keyed by (environmentId, name); present on both schemas.
waitpointTag: RunOpsDelegate<"upsert" | "findMany">;
$queryRaw: PrismaClient["$queryRaw"];
$queryRawUnsafe: PrismaClient["$queryRawUnsafe"];
$executeRaw: PrismaClient["$executeRaw"];
}
@@ -1691,11 +1692,16 @@ export class PostgresRunStore implements RunStore {
return [];
}
const prisma = (client ?? this.readOnlyPrisma) as RunOpsCapableClient;
const branches = args.idempotencyKeys.map(
(key) =>
Prisma.sql`SELECT "friendlyId", "idempotencyKey", "idempotencyKeyExpiresAt" FROM "TaskRun" WHERE "runtimeEnvironmentId" = ${args.runtimeEnvironmentId} AND "taskIdentifier" = ${args.taskIdentifier} AND "idempotencyKey" = ${key}`
const params: string[] = [];
const branches = args.idempotencyKeys.map((key) => {
const base = params.length;
params.push(args.runtimeEnvironmentId, args.taskIdentifier, key);
return `SELECT "friendlyId", "idempotencyKey", "idempotencyKeyExpiresAt" FROM "TaskRun" WHERE "runtimeEnvironmentId" = $${base + 1} AND "taskIdentifier" = $${base + 2} AND "idempotencyKey" = $${base + 3}`;
});
return prisma.$queryRawUnsafe<IdempotencyKeyRunMatch[]>(
branches.join(" UNION ALL "),
...params
);
return prisma.$queryRaw<IdempotencyKeyRunMatch[]>(Prisma.join(branches, " UNION ALL "));
}
// --- run-ops persistence ---