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

未關閉
#4,819 2 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 1 天內回覆

還沒有人認領這個 Issue。

  • #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 中的 BatchTriggerV3Service.#prepareRunData(約第 452-469 行)開始,並與 apps/webapp/app/runEngine/concerns/idempotencyKeys.server.ts(約第 463 行)中的 IdempotencyKeyConcern.handleExistingRun 進行比較,後者已經呼叫了 shouldIdempotencyKeyBeCleared(status)。接著追蹤 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 小時
30 天內合併 PR
17

環境準備

這個專案沒有提供開發容器、Dockerfile 或貢獻指南,環境需要你自己搭建:先看它的 README,通用步驟見我們的新手貢獻指南。

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

triggerdotdev/trigger.dev 的其他 Issue

查看 triggerdotdev/trigger.dev 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。