@@ -51,6 +51,7 @@ import type {
5151} from '@/lib/table/types'
5252import {
5353 buildWorkflowGroupExecutionCorrelation ,
54+ isManualQueuedMarker ,
5455 type QueuedWorkflowGroupCellPayload ,
5556 type WorkflowGroupCellPayload ,
5657} from '@/lib/table/workflow-columns'
@@ -133,6 +134,7 @@ export async function terminalizeAbortedQueuedCarrierMarker(
133134 executionId : executionState . executionId ,
134135 jobId : executionState . jobId ,
135136 workflowId : executionState . workflowId ,
137+ isManualRun : payload . isManualRun ,
136138 error : executionState . error ,
137139 runningBlockIds : executionState . runningBlockIds ,
138140 updatedAt : new Date ( ) ,
@@ -162,6 +164,7 @@ export async function terminalizeAbortedQueuedCarrierMarker(
162164 executionId : executionState . executionId ?? null ,
163165 jobId : null ,
164166 error : executionState . error ?? null ,
167+ isManualRun : payload . isManualRun ,
165168 } )
166169 return true
167170}
@@ -278,8 +281,8 @@ export async function executeWorkflowGroupCellJob(
278281 if ( ! freshRow ) break
279282 const next = pickNextEligibleGroupForRow ( freshTable , freshRow )
280283 if ( ! next ) break
281- // Only re-drive a genuine queued marker (an explicit run request whose
282- // cell-task bailed during our release window). The inner cascade loop has
284+ // Only re-drive a genuine queued marker (a dispatched run whose cell-task
285+ // bailed during our release window). The inner cascade loop has
283286 // already drained every auto-eligible group, so re-driving a non-marker
284287 // group here would re-run forever — e.g. a group that completed with empty
285288 // outputs stays auto-eligible (the inner loop excludes it via
@@ -294,6 +297,7 @@ export async function executeWorkflowGroupCellJob(
294297 // Re-derive so a workflow group after an enrichment group doesn't keep a stale enrichmentId.
295298 enrichmentId : next . enrichmentId ,
296299 executionId : generateId ( ) ,
300+ isManualRun : isManualQueuedMarker ( nextExec ) ,
297301 }
298302 }
299303 } finally {
@@ -315,6 +319,7 @@ export async function runRowCascadeLoop(
315319
316320 let currentGroupId = payload . groupId
317321 let currentWorkflowId = payload . workflowId
322+ let currentIsManualRun = payload . isManualRun
318323 // Fresh executionId per iteration: SQL guard rejects writes whose id ≠
319324 // row.executions[gid].executionId, so we need a new claim per group.
320325 let currentExecutionId = payload . executionId
@@ -327,6 +332,7 @@ export async function runRowCascadeLoop(
327332 groupId : currentGroupId ,
328333 workflowId : currentWorkflowId ,
329334 executionId : currentExecutionId ,
335+ isManualRun : currentIsManualRun ,
330336 } ,
331337 signal
332338 )
@@ -350,6 +356,7 @@ export async function runRowCascadeLoop(
350356 groupId : currentGroupId ,
351357 workflowId : currentWorkflowId ,
352358 executionId : currentExecutionId ,
359+ isManualRun : currentIsManualRun ,
353360 } ,
354361 signal ,
355362 freshTable ,
@@ -366,6 +373,8 @@ export async function runRowCascadeLoop(
366373 if ( ! freshRow ) break
367374 const next = pickNextEligibleGroupForRow ( freshTable , freshRow , currentGroupId )
368375 if ( ! next ) break
376+ const nextExec = freshRow . executions ?. [ next . id ]
377+ currentIsManualRun = isManualQueuedMarker ( nextExec )
369378 currentGroupId = next . id
370379 currentWorkflowId = next . workflowId
371380 currentExecutionId = generateId ( )
@@ -411,7 +420,16 @@ async function runWorkflowAndWriteTerminal(
411420 )
412421 const { stashCellContextForResume } = await import ( '@/lib/table/workflow-columns' )
413422
414- const cellCtx = { tableId, rowId, workspaceId, groupId, executionId, requestId, table }
423+ const cellCtx = {
424+ tableId,
425+ rowId,
426+ workspaceId,
427+ groupId,
428+ executionId,
429+ isManualRun : payload . isManualRun ,
430+ requestId,
431+ table,
432+ }
415433 const writeState = (
416434 executionState : RowExecutionMetadata ,
417435 dataPatch ?: RowData ,
0 commit comments