bug: batchTrigger returns stale failed runs instead of re-triggering when idempotency key points to a dead run
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 30/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Estancado
- Stack tecnológico
- postgresql, typescript
- Área
- backend
Línea de trabajo
Empieza en BatchTriggerV3Service.#prepareRunData en apps/webapp/app/v3/services/batchTriggerV3.server.ts (alrededor de las líneas 452-469) y compáralo con IdempotencyKeyConcern.handleExistingRun en apps/webapp/app/runEngine/concerns/idempotencyKeys.server.ts (~línea 463), que ya llama a shouldIdempotencyKeyBeCleared(status). Después rastrea IdempotencyKeyRunMatch en internal-packages/run-store/src/types.ts y findRunsByIdempotencyKeys en internal-packages/run-store/src/PostgresRunStore.ts, donde status falta en el SELECT. Listo significa que batchTrigger devuelve isCached:false y crea un run nuevo para los estados de fallo terminal, verificado por la reproducción en el issue; ten en cuenta que los PRs #4912 y #4913 se cerraron sin fusionarse, así que comprueba por qué antes de empezar.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Bug Description
batchTrigger and single trigger behave differently when an idempotency key points to a run that already failed.
With single trigger, if a previous run with the same idempotency key ended up in a terminal failure state (CRASHED, SYSTEM_FAILURE, TIMED_OUT, EXPIRED, COMPLETED_WITH_ERRORS, INTERRUPTED), it correctly clears the key and creates a fresh run. This works because IdempotencyKeyConcern.handleExistingRun calls shouldIdempotencyKeyBeCleared(status) before deciding whether to return a cached result.
With batchTrigger, that same check is missing. The batch path in BatchTriggerV3Service.#prepareRunData only checks time-based expiration (idempotencyKeyExpiresAt < now), so a failed run gets returned as isCached: true — silently handing back a dead run that will never produce output.
Steps to Reproduce
// 1. A task that always fails
export const failingTask = task({
id: "failing-task",
run: async () => { throw new Error("boom"); },
});
// 2. Batch trigger with idempotency key
await tasks.batchTrigger("failing-task", [
{ payload: {}, options: { idempotencyKey: "key-1" } },
]);
// Wait for the run to reach CRASHED / COMPLETED_WITH_ERRORS...
// 3. Trigger again with the same key
await tasks.batchTrigger("failing-task", [
{ payload: {}, options: { idempotencyKey: "key-1" } },
]);
// Expected: isCached: false (fresh run created)
// Actual: isCached: true (dead run returned)
Compare with single trigger — this already works correctly:
await tasks.trigger("failing-task", {}, { idempotencyKey: "key-2" });
// Wait for failure...
await tasks.trigger("failing-task", {}, { idempotencyKey: "key-2" });
// Correctly returns isCached: false and creates a new run
Root Cause
The SQL query in PostgresRunStore.findRunsByIdempotencyKeys never selects the status column, and the IdempotencyKeyRunMatch type doesn't include it. So #prepareRunData has no way to check whether a matched run is in a clearable failure state — it can only check time-based expiration.
The single-trigger path works because IdempotencyKeyConcern.handleExistingRun fetches the full run record and calls shouldIdempotencyKeyBeCleared(status) at idempotencyKeys.server.ts:463. The batch path at batchTriggerV3.server.ts:452-469 skips this entirely.
| Check | Single trigger | Batch trigger |
|---|---|---|
| Time-based expiry | ✅ idempotencyKeyExpiresAt < now |
✅ idempotencyKeyExpiresAt < now |
| Failure status check | ✅ shouldIdempotencyKeyBeCleared(status) |
❌ Missing |
Affected Statuses
These statuses should clear the idempotency key and re-trigger (works for single trigger, broken for batch):
CRASHEDSYSTEM_FAILURETIMED_OUTEXPIREDCOMPLETED_WITH_ERRORSINTERRUPTED
Suggested Fix
I already wrote and tested a patch for this. My PR #4818 was auto-closed since I'm not a vouched contributor, but the fix is ready on my fork if you'd like to pull it in or use it as reference:
Branch: Jaimin2687:fix/batch-trigger-idempotency-key-status-check
The fix is three small changes:
internal-packages/run-store/src/types.ts— Addstatus: stringtoIdempotencyKeyRunMatchinternal-packages/run-store/src/PostgresRunStore.ts— Add"status"to the raw SQL SELECT infindRunsByIdempotencyKeysapps/webapp/app/v3/services/batchTriggerV3.server.ts— ImportshouldIdempotencyKeyBeClearedand add the guard after the expiry check in#prepareRunData, mirroring the single-trigger path
The branch also includes 21 unit tests and 2 integration tests (testcontainers) covering all TaskRunStatus values. Everything passes typecheck, build, format, and lint.
Environment
- trigger.dev
mainbranch (as of Aug 28, 2025) - Affects all environments (dev, staging, production)
- Backend only, no UI impact
- Lenguaje dominante
- TypeScript
- Estrellas
- 16.5k
- Forks
- 1.5k
- Merge medio
- 1 d 15 h
- PR fusionados (30 d)
- 17
Preparar el entorno
Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de triggerdotdev/trigger.dev
-
bug: retry.fetch ignores the caller's abort signal and retries aborted requestsQuizá libre de nuevo Un pull request para esta issue se cerró sin fusionarse. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
triggerdotdev/trigger.dev#5116 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
bug: Chat transport crashes on Safari < 17.4: AbortSignal.any is not a functionQuizá libre de nuevo Un pull request para esta issue se cerró sin fusionarse. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 83/100
triggerdotdev/trigger.dev#5098 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
bug: Side menu items lose their active state on detail pagesQuizá libre de nuevo Un pull request para esta issue se cerró sin fusionarse. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
triggerdotdev/trigger.dev#5088 ·
Los mantenedores suelen responder en 1 día
-
bug: RBAC loader logs "plugin found but failed to load" when @triggerdotdev/plugins is simply not installedQuizá libre de nuevo @itsfaryal la tomó hace 17 días y no hay ningún pull request abierto. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
triggerdotdev/trigger.dev#5083 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 83/100
triggerdotdev/trigger.dev#5072 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de triggerdotdev/trigger.dev
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día
-
bug platform-android
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
Termix-SSH/Mobile#146 · 1 comentario ·
Los mantenedores suelen responder en 2 días
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 66/100
devswha/herdr-web-ui#3875 ·
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
-
area: runtime chore cli consistency dependencies developer-experience difficulty: easy discovery documentation enhancement good first issue help wanted hygiene json-output packaging priority: P3 tests version
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día