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

オープン
#4,819 コメント 2 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

  • #4912 @breken-ai による — マージされずにクローズ
  • #4913 @itzzdev09 による — マージされずにクローズ
  • #5014 @61465 による — マージされずにクローズ

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
30/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
停滞
技術スタック
postgresql, typescript
領域
backend

調査の方向性

apps/webapp/app/v3/services/batchTriggerV3.server.ts(452〜469 行目あたり)の BatchTriggerV3Service.#prepareRunData から始めて、すでに shouldIdempotencyKeyBeCleared(status) を呼び出している apps/webapp/app/runEngine/concerns/idempotencyKeys.server.ts(約 463 行目)の IdempotencyKeyConcern.handleExistingRun と比較する。次に internal-packages/run-store/src/types.ts の IdempotencyKeyRunMatch と internal-packages/run-store/src/PostgresRunStore.ts の findRunsByIdempotencyKeys を追跡し、そちらでは SELECT から status が欠落している。完了の条件は、batchTrigger が isCached:false を返し、終端的な失敗ステータスに対して新しい Run を作成することであり、Issue の再現手順で検証する。なお PR #4912 と #4913 はマージされずにクローズされているので、着手前に理由を確認すること。

索引モデルが issue の本文から書いたものです。

説明

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
主要言語
TypeScript
スター
16.5k
フォーク
1.5k
平均マージ
1日 15時間
マージ済み PR(30日)
17

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

triggerdotdev/trigger.dev のほかの issue

triggerdotdev/trigger.dev の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。