Skip to content

Commit 655269e

Browse files
fix(table): seed dispatch overlay on Run; surface batch-enqueue failures as error
- useRunColumn.onSuccess invalidates the activeDispatches query so the resolveCellExec queued overlay populates immediately for ahead-of-cursor rows (scrolled-in / refetched), instead of waiting for the first dispatch SSE. Targeted at activeDispatches only — the rows cache stays owned by useTableEventStream. - On batchEnqueueAndWait failure, dispatcherStep now flips the orphan pre-stamps to a terminal `error` state and emits a cell SSE event, rather than deleting them. The cursor still advances past the window, but the dropped cells are now visible (Error pill) instead of silently empty, stay out of the in-flight set, and re-run on the next manual run.
1 parent 5c657e7 commit 655269e

2 files changed

Lines changed: 38 additions & 13 deletions

File tree

apps/sim/hooks/queries/tables.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,9 +1377,17 @@ export function useRunColumn({ workspaceId, tableId }: RowMutationContext) {
13771377
onError: (_err, _variables, context) => {
13781378
if (context?.snapshots) restoreCachedWorkflowCells(queryClient, context.snapshots)
13791379
},
1380-
// No reconciliation here — useTableEventStream is the source of truth for
1381-
// post-mutation cache state, and a refetch would race its incremental
1382-
// patches.
1380+
onSuccess: () => {
1381+
// Seed the active-dispatch overlay from the server. `runWorkflowColumn`
1382+
// inserts the dispatch row synchronously before responding, so the
1383+
// refetch picks it up immediately — this drives `resolveCellExec`'s
1384+
// queued overlay for rows ahead of the cursor (scrolled-in / refetched
1385+
// rows that have no real exec yet) without waiting for the first
1386+
// `kind: 'dispatch'` SSE. Targeted at activeDispatches only — NOT the
1387+
// rows cache, which `useTableEventStream` owns (a rows refetch would
1388+
// race its incremental patches).
1389+
void queryClient.invalidateQueries({ queryKey: tableKeys.activeDispatches(tableId) })
1390+
},
13831391
})
13841392
}
13851393

apps/sim/lib/table/dispatcher.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -399,16 +399,21 @@ export async function dispatcherStep(dispatchId: string): Promise<DispatcherStep
399399
logger.error(`[${dispatchId}] batch dispatch failed`, {
400400
error: toError(err).message,
401401
})
402-
// Reset the orphan pre-stamps so the cells don't render "Queued" forever.
403-
// Without this sweep, `pending + null executionId` rows stay until a user
404-
// re-triggers the row. The classifyEligibility carve-out lets future
405-
// dispatchers re-claim, but the current dispatch has already advanced
406-
// its cursor past these rows. Deleting the pre-stamps lets the next
407-
// explicit Run pick them up cleanly.
402+
// The enqueue failed (e.g. trigger.dev API down) — these cells never
403+
// ran. The cursor still advances past this window below, so they won't
404+
// be retried in this dispatch. Flip the orphan pre-stamps to a terminal
405+
// `error` state (with an SSE event) instead of deleting them: the
406+
// failure stays visible to the user (Error pill, not a silently-empty
407+
// cell), the cells aren't stuck `pending` forever, and `error` cells
408+
// re-run on the next manual run (classifyEligibility treats error as
409+
// eligible for manual / incomplete). Guarded on `pending + null
410+
// executionId` so we only touch our own un-claimed pre-stamps.
411+
const failedAt = new Date()
408412
await Promise.allSettled(
409-
pendingRuns.map((p) =>
410-
db
411-
.delete(tableRowExecutions)
413+
pendingRuns.map(async (p) => {
414+
const updated = await db
415+
.update(tableRowExecutions)
416+
.set({ status: 'error', error: 'Failed to enqueue run', updatedAt: failedAt })
412417
.where(
413418
and(
414419
eq(tableRowExecutions.rowId, p.rowId),
@@ -417,7 +422,19 @@ export async function dispatcherStep(dispatchId: string): Promise<DispatcherStep
417422
sql`${tableRowExecutions.executionId} IS NULL`
418423
)
419424
)
420-
)
425+
.returning({ rowId: tableRowExecutions.rowId })
426+
if (updated.length === 0) return
427+
await appendTableEvent({
428+
kind: 'cell',
429+
tableId: dispatch.tableId,
430+
rowId: p.rowId,
431+
groupId: p.groupId,
432+
status: 'error',
433+
executionId: null,
434+
jobId: null,
435+
error: 'Failed to enqueue run',
436+
})
437+
})
421438
)
422439
}
423440
}

0 commit comments

Comments
 (0)