bug: batchTrigger returns stale failed runs instead of re-triggering when idempotency key points to a dead run

Ouverte
#4,819 2 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Les mainteneurs répondent en général sous 1 jour

Personne n'a encore pris cette issue.

  • #4912 par @breken-ai — fermée sans fusion
  • #4913 par @itzzdev09 — fermée sans fusion
  • #5014 par @61465 — fermée sans fusion

Évaluation

Difficulté
3/5
Temps estimé
1-2 jours
Accessibilité débutants
30/100
Type d'issue
Bug
Clarté
Clairement spécifiée
Activité
À l'abandon
Stack technique
postgresql, typescript
Domaine
backend

Piste de recherche

Commence par BatchTriggerV3Service.#prepareRunData dans apps/webapp/app/v3/services/batchTriggerV3.server.ts (lignes environ 452-469) et compare-le à IdempotencyKeyConcern.handleExistingRun dans apps/webapp/app/runEngine/concerns/idempotencyKeys.server.ts (~ligne 463), qui appelle déjà shouldIdempotencyKeyBeCleared(status). Ensuite, suis IdempotencyKeyRunMatch dans internal-packages/run-store/src/types.ts et findRunsByIdempotencyKeys dans internal-packages/run-store/src/PostgresRunStore.ts, où status est absent du SELECT. Terminé signifie que batchTrigger renvoie isCached:false et crée un nouveau run pour les statuts d'échec terminal, vérifié par la reproduction décrite dans l'issue ; note que les PRs #4912 et #4913 ont été fermées sans être fusionnées, alors vérifie pourquoi avant de commencer.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

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):

  • CRASHED
  • SYSTEM_FAILURE
  • TIMED_OUT
  • EXPIRED
  • COMPLETED_WITH_ERRORS
  • INTERRUPTED

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:

  1. internal-packages/run-store/src/types.ts — Add status: string to IdempotencyKeyRunMatch
  2. internal-packages/run-store/src/PostgresRunStore.ts — Add "status" to the raw SQL SELECT in findRunsByIdempotencyKeys
  3. apps/webapp/app/v3/services/batchTriggerV3.server.ts — Import shouldIdempotencyKeyBeCleared and 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 main branch (as of Aug 28, 2025)
  • Affects all environments (dev, staging, production)
  • Backend only, no UI impact
Langage dominant
TypeScript
Étoiles
16.5k
Forks
1.5k
Merge moyen
1 j 15 h
PR mergées (30 j)
17

Préparer son environnement

Ce projet ne fournit ni conteneur de développement, ni Dockerfile, ni guide de contribution : l'installation est à votre charge. Commencez par son README, et consultez notre guide de la première contribution pour les étapes générales.

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de triggerdotdev/trigger.dev

Toutes les issues de triggerdotdev/trigger.dev

Issues similaires

Plus d'issues TypeScript

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.