Summary
On self-hosted v4.4.6, batchTrigger appears to drop the per-item idempotencyKey for the items in batches that are re-driven after the batch rate limiter trips, so those runs are created un-deduplicated.
What we observed
We fan out ~6,000+ runs via a single tasks.batchTrigger(...) where every item carries a distinct idempotencyKey created with idempotencyKeys.create([...], { scope: 'global' }) and idempotencyKeyTTL.
When the fan-out exceeds the environment's batch token bucket (BATCH_RATE_LIMIT_MAX), the two-phase batch path throws:
BatchTriggerError: Failed to create batch with N items: Rate limit exceeded - retry after Ns (isRateLimited: true)
BatchTriggerError: Failed to stream items for batch <id> (N items): Batch <id> is already sealed and cannot accept more items
(stack points into executeBatchTwoPhase in @trigger.dev/sdk/src/v3/shared.ts).
Querying the resulting runs, the early batches have idempotency_key populated, while the batches created/re-driven after the rate-limit + "sealed" errors have an empty idempotency_key — all with the same sdk_version 4.4.6. In one run: 945 keyed runs across 3 batches, 5,066 keyless runs across 18 batches. The keyless runs bypass dedup and re-fire on every subsequent trigger cycle.
Expected
Per-item idempotencyKey (and TTL) should be preserved across batch sealing / rate-limit re-drive, so deduplication holds regardless of whether the batch rate limiter is hit.
Repro sketch
- Set the environment's
BATCH_RATE_LIMIT_MAX below your intended batch size (e.g. default 1200).
batchTrigger more items than the bucket, each with a distinct global-scope idempotencyKey.
- Observe
Rate limit exceeded + Batch ... is already sealed errors, then inspect the created runs: the post-seal batches have no idempotency key.
Environment
- Self-hosted, Helm chart + images
v4.4.6
- SDK
@trigger.dev/sdk 4.4.6
Summary
On self-hosted v4.4.6,
batchTriggerappears to drop the per-itemidempotencyKeyfor the items in batches that are re-driven after the batch rate limiter trips, so those runs are created un-deduplicated.What we observed
We fan out ~6,000+ runs via a single
tasks.batchTrigger(...)where every item carries a distinctidempotencyKeycreated withidempotencyKeys.create([...], { scope: 'global' })andidempotencyKeyTTL.When the fan-out exceeds the environment's batch token bucket (
BATCH_RATE_LIMIT_MAX), the two-phase batch path throws:BatchTriggerError: Failed to create batch with N items: Rate limit exceeded - retry after Ns(isRateLimited: true)BatchTriggerError: Failed to stream items for batch <id> (N items): Batch <id> is already sealed and cannot accept more items(stack points into
executeBatchTwoPhasein@trigger.dev/sdk/src/v3/shared.ts).Querying the resulting runs, the early batches have
idempotency_keypopulated, while the batches created/re-driven after the rate-limit + "sealed" errors have an emptyidempotency_key— all with the samesdk_version4.4.6. In one run: 945 keyed runs across 3 batches, 5,066 keyless runs across 18 batches. The keyless runs bypass dedup and re-fire on every subsequent trigger cycle.Expected
Per-item
idempotencyKey(and TTL) should be preserved across batch sealing / rate-limit re-drive, so deduplication holds regardless of whether the batch rate limiter is hit.Repro sketch
BATCH_RATE_LIMIT_MAXbelow your intended batch size (e.g. default 1200).batchTriggermore items than the bucket, each with a distinct global-scopeidempotencyKey.Rate limit exceeded+Batch ... is already sealederrors, then inspect the created runs: the post-seal batches have no idempotency key.Environment
v4.4.6@trigger.dev/sdk4.4.6