-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
bug: task.batchTriggerAndWait(array) silently drops per-item debounce options #3304
Description
Provide environment information
System:
OS: Linux 6.17 Ubuntu 24.04.4 LTS 24.04.4 LTS (Noble Numbat)
CPU: (6) arm64 unknown
Memory: 14.04 GB / 31.27 GB
Shell: 5.2.21 - /bin/bash
Binaries:
Node: 24.13.1 - /home/main/n/bin/node
npm: 11.8.0 - /home/main/n/bin/npm
pnpm: 10.28.2 - /home/main/.local/share/pnpm/pnpm
SDK version: @trigger.dev/sdk@4.4.3
Describe the bug
When calling task.batchTriggerAndWait(items) with an array of items that include debounce in their options, the debounce is silently ignored. Every item creates a separate run instead of coalescing by debounce key. The TypeScript types accept debounce in per-item options, so there's no compile-time warning.
The same items passed via an async iterable (task.batchTriggerAndWait(asyncGenerator())) correctly apply debounce.
Root cause: In packages/trigger-sdk/src/v3/shared.ts, the batchTriggerAndWait_internal function's array branch (around line 2484) constructs per-item options by explicitly listing fields, and debounce is missing from the list:
lockToVersion, queue, concurrencyKey, test, payloadType, delay, ttl,
tags, maxAttempts, metadata, maxDuration, idempotencyKey,
idempotencyKeyTTL, idempotencyKeyOptions, machine, priority, regionThe streaming transforms (e.g., transformSingleTaskBatchItemsStreamForWait) correctly include debounce: item.options?.debounce.
Reproduction repo
To reproduce
import { task } from "@trigger.dev/sdk";
const myTask = task({
id: "my-task",
run: async (payload: { id: string }) => {
await new Promise((r) => setTimeout(r, 5000));
return { done: true };
},
});
// Parent task
const parent = task({
id: "parent",
run: async () => {
// These should coalesce into 1 run per key, but each creates a separate run
const results = await myTask.batchTriggerAndWait([
{ payload: { id: "a" }, options: { debounce: { key: "same-key", delay: "30s", mode: "trailing" } } },
{ payload: { id: "b" }, options: { debounce: { key: "same-key", delay: "30s", mode: "trailing" } } },
{ payload: { id: "c" }, options: { debounce: { key: "same-key", delay: "30s", mode: "trailing" } } },
]);
// Expected: 1 run (all 3 debounced to same key)
// Actual: 3 separate runs
console.log("Runs:", results.runs.length);
},
});Workaround: Use an async iterable instead of an array:
async function* items() {
yield { payload: { id: "a" }, options: { debounce: { key: "same-key", delay: "30s", mode: "trailing" } } };
yield { payload: { id: "b" }, options: { debounce: { key: "same-key", delay: "30s", mode: "trailing" } } };
yield { payload: { id: "c" }, options: { debounce: { key: "same-key", delay: "30s", mode: "trailing" } } };
}
const results = await myTask.batchTriggerAndWait(items());
// Correctly produces 1 runAdditional information
The fix should be a one-line addition in batchTriggerAndWait_internal's array branch — add debounce: item.options?.debounce to the per-item options object, matching what the streaming transforms already do.
Affected paths:
task.batchTriggerAndWait(array)— brokentasks.batchTriggerAndWait(id, array)— broken (same internal function)
Working paths:
task.batchTriggerAndWait(asyncIterable)— worksbatch.triggerAndWait(items)(heterogeneous) — workstask.batchTrigger(array)(fire-and-forget) — works