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

Đang mở
#4,819 2 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

  • #4912 của @breken-ai — đã đóng, không merge
  • #4913 của @itzzdev09 — đã đóng, không merge
  • #5014 của @61465 — đã đóng, không merge

Đánh giá

Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức phù hợp với người mới
30/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Đình trệ
Công nghệ
postgresql, typescript
Lĩnh vực
backend

Hướng nghiên cứu

Bắt đầu từ BatchTriggerV3Service.#prepareRunData trong apps/webapp/app/v3/services/batchTriggerV3.server.ts (khoảng các dòng 452-469) và so sánh với IdempotencyKeyConcern.handleExistingRun trong apps/webapp/app/runEngine/concerns/idempotencyKeys.server.ts (~dòng 463), nơi đã gọi shouldIdempotencyKeyBeCleared(status). Sau đó, lần theo IdempotencyKeyRunMatch trong internal-packages/run-store/src/types.ts và findRunsByIdempotencyKeys trong internal-packages/run-store/src/PostgresRunStore.ts, nơi status bị thiếu trong SELECT. Hoàn thành nghĩa là batchTrigger trả về isCached:false và tạo một run mới cho các trạng thái lỗi terminal, đã được xác minh bằng cách tái hiện trong issue; lưu ý rằng PR #4912 và #4913 đã bị đóng mà không merge, vì vậy hãy kiểm tra lý do trước khi bắt đầu.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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
Ngôn ngữ chính
TypeScript
Star
16.5k
Fork
1.5k
Merge trung bình
1 ngày 15 giờ
Pull request đã merge (30 ngày)
17

Chuẩn bị môi trường

Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của triggerdotdev/trigger.dev

Tất cả issue của triggerdotdev/trigger.dev

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.