Skip to content

bug: task.batchTriggerAndWait(array) silently drops per-item debounce options #3304

@danielr18

Description

@danielr18

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, region

The streaming transforms (e.g., transformSingleTaskBatchItemsStreamForWait) correctly include debounce: item.options?.debounce.

Reproduction repo

https://github.com/triggerdotdev/trigger.dev/blob/2ba77d8/packages/trigger-sdk/src/v3/shared.ts#L2484-L2510

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 run

Additional 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) — broken
  • tasks.batchTriggerAndWait(id, array) — broken (same internal function)

Working paths:

  • task.batchTriggerAndWait(asyncIterable) — works
  • batch.triggerAndWait(items) (heterogeneous) — works
  • task.batchTrigger(array) (fire-and-forget) — works

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions