@@ -160,23 +160,26 @@ function createRunEngine() {
160160 } ,
161161 // BatchQueue with DRR scheduling for fair batch processing
162162 // Consumers are controlled by options.worker.disabled (same as main worker)
163- batchQueue : env . BATCH_TRIGGER_WORKER_ENABLED === "true" ? {
164- redis : {
165- keyPrefix : "engine:" ,
166- port : env . BATCH_TRIGGER_WORKER_REDIS_PORT ?? undefined ,
167- host : env . BATCH_TRIGGER_WORKER_REDIS_HOST ?? undefined ,
168- username : env . BATCH_TRIGGER_WORKER_REDIS_USERNAME ?? undefined ,
169- password : env . BATCH_TRIGGER_WORKER_REDIS_PASSWORD ?? undefined ,
170- enableAutoPipelining : true ,
171- ...( env . BATCH_TRIGGER_WORKER_REDIS_TLS_DISABLED === "true" ? { } : { tls : { } } ) ,
172- } ,
173- drr : {
174- quantum : env . BATCH_QUEUE_DRR_QUANTUM ,
175- maxDeficit : env . BATCH_QUEUE_MAX_DEFICIT ,
176- } ,
177- consumerCount : env . BATCH_QUEUE_CONSUMER_COUNT ,
178- consumerIntervalMs : env . BATCH_QUEUE_CONSUMER_INTERVAL_MS ,
179- } : undefined ,
163+ batchQueue :
164+ env . BATCH_TRIGGER_WORKER_ENABLED === "true"
165+ ? {
166+ redis : {
167+ keyPrefix : "engine:" ,
168+ port : env . BATCH_TRIGGER_WORKER_REDIS_PORT ?? undefined ,
169+ host : env . BATCH_TRIGGER_WORKER_REDIS_HOST ?? undefined ,
170+ username : env . BATCH_TRIGGER_WORKER_REDIS_USERNAME ?? undefined ,
171+ password : env . BATCH_TRIGGER_WORKER_REDIS_PASSWORD ?? undefined ,
172+ enableAutoPipelining : true ,
173+ ...( env . BATCH_TRIGGER_WORKER_REDIS_TLS_DISABLED === "true" ? { } : { tls : { } } ) ,
174+ } ,
175+ drr : {
176+ quantum : env . BATCH_QUEUE_DRR_QUANTUM ,
177+ maxDeficit : env . BATCH_QUEUE_MAX_DEFICIT ,
178+ } ,
179+ consumerCount : env . BATCH_QUEUE_CONSUMER_COUNT ,
180+ consumerIntervalMs : env . BATCH_QUEUE_CONSUMER_INTERVAL_MS ,
181+ }
182+ : undefined ,
180183 } ) ;
181184
182185 // Set up BatchQueue callbacks if enabled
@@ -187,6 +190,31 @@ function createRunEngine() {
187190 return engine ;
188191}
189192
193+ /**
194+ * Normalize the payload from BatchQueue.
195+ * The payload might be a JSON string if the SDK sent it pre-serialized.
196+ * If it's a JSON string and payloadType is "application/json", parse it
197+ * to avoid double-stringification in DefaultPayloadProcessor.
198+ */
199+ function normalizePayload ( payload : unknown , payloadType ?: string ) : unknown {
200+ // Only normalize for JSON payloads
201+ if ( payloadType !== "application/json" && payloadType !== undefined ) {
202+ return payload ;
203+ }
204+
205+ // If payload is a string, try to parse it as JSON
206+ if ( typeof payload === "string" ) {
207+ try {
208+ return JSON . parse ( payload ) ;
209+ } catch {
210+ // If it's not valid JSON, return as-is
211+ return payload ;
212+ }
213+ }
214+
215+ return payload ;
216+ }
217+
190218/**
191219 * Set up the BatchQueue processing callbacks.
192220 * These handle creating runs from batch items and completing batches.
@@ -197,6 +225,9 @@ function setupBatchQueueCallbacks(engine: RunEngine) {
197225 try {
198226 const triggerTaskService = new TriggerTaskService ( ) ;
199227
228+ // Normalize payload to avoid double-stringification
229+ const payload = normalizePayload ( item . payload , item . payloadType ) ;
230+
200231 const result = await triggerTaskService . call (
201232 item . task ,
202233 {
@@ -208,7 +239,7 @@ function setupBatchQueueCallbacks(engine: RunEngine) {
208239 project : { id : meta . projectId } ,
209240 } as AuthenticatedEnvironment ,
210241 {
211- payload : item . payload ,
242+ payload,
212243 options : {
213244 ...( item . options as Record < string , unknown > ) ,
214245 payloadType : item . payloadType ,
0 commit comments