Skip to content

Commit 464f14a

Browse files
committed
fix(mothership): answer a paused Chat workflow tool still running when its form was submitted before Chat read the pause
1 parent 83d4c9c commit 464f14a

2 files changed

Lines changed: 57 additions & 5 deletions

File tree

‎apps/sim/lib/mothership/request/tools/client.ts‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
type AsyncTerminalCompletionSnapshot,
77
getTerminalConfirmationStatus,
88
isAsyncTerminalConfirmationStatus,
9+
isTerminalAsyncStatus,
910
} from '@/lib/mothership/async-runs/lifecycle'
1011
import { replaceTerminalAsyncToolCallResult } from '@/lib/mothership/async-runs/repository'
1112
import { MothershipStreamV1ToolOutcome } from '@/lib/mothership/generated/mothership-stream-v1'
@@ -42,6 +43,7 @@ import {
4243
getHumanPauseResumeUrl,
4344
getTrustedWorkflowToolExecution,
4445
getWorkflowExecutionCopilotToolCallId,
46+
getWorkflowExecutionLogStatus,
4547
} from '@/lib/workflows/executor/execution-state'
4648
import type { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
4749

@@ -376,21 +378,33 @@ export async function waitForWorkflowToolCompletion({
376378
completion.data.status === WORKFLOW_TOOL_PAUSED_STATUS
377379
) {
378380
// Re-read rather than trust the confirmation; a run resumed since then reads as finished.
379-
const [resumeUrl, boundToolCallId] = await Promise.all([
381+
const [resumeUrl, boundToolCallId, logStatus] = await Promise.all([
380382
getHumanPauseResumeUrl(executionId, workflowId),
381383
getWorkflowExecutionCopilotToolCallId(executionId, workflowId),
384+
getWorkflowExecutionLogStatus(executionId, workflowId),
382385
]).catch((error) => {
383386
logger.warn('Failed to read paused workflow tool execution', {
384387
toolCallId,
385388
workflowId,
386389
executionId,
387390
error: getErrorMessage(error),
388391
})
389-
return [null, undefined] as const
392+
return [null, undefined, undefined] as const
390393
})
391-
if (resumeUrl && boundToolCallId === toolCallId) {
392-
toolRegistry?.markIncomplete('client-tool-completion-deferred')
393-
return buildPausedWorkflowToolCompletion(workflowId, executionId, resumeUrl)
394+
if (boundToolCallId === toolCallId) {
395+
if (resumeUrl) {
396+
toolRegistry?.markIncomplete('client-tool-completion-deferred')
397+
return buildPausedWorkflowToolCompletion(workflowId, executionId, resumeUrl)
398+
}
399+
// The person answered before this read and the resumed run has no result yet.
400+
if (logStatus !== undefined && !isTerminalAsyncStatus(logStatus)) {
401+
toolRegistry?.markIncomplete('client-tool-completion-deferred')
402+
return structuralWorkflowCompletion(
403+
ASYNC_TOOL_CONFIRMATION_STATUS.background,
404+
workflowId,
405+
executionId
406+
)
407+
}
394408
}
395409
}
396410

‎apps/sim/lib/mothership/request/tools/workflow-client-settlement.integration.ts‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,44 @@ describe.runIf(Boolean(redisUrl))('settled client-claimed workflow tools', () =>
530530
expect(await toolRow(toolCallId)).toMatchObject({ status: 'completed', claimedBy: null })
531531
})
532532

533+
it('answers a paused call still running when the person submitted before Chat read the pause', async () => {
534+
const { toolCallId, executionId } = await claimedAndPausedUnreported('human')
535+
await reportSettledClientWorkflowTool({ toolCallId, executionId, workflowId })
536+
// The resume took the pause point and the resumed run is going.
537+
const [pausedRow] = await db
538+
.select({ id: pausedExecutions.id, pausePoints: pausedExecutions.pausePoints })
539+
.from(pausedExecutions)
540+
.where(eq(pausedExecutions.executionId, executionId))
541+
await db
542+
.update(pausedExecutions)
543+
.set({
544+
pausePoints: {
545+
[APPROVAL_CONTEXT_ID]: {
546+
...(pausedRow.pausePoints as Record<string, Record<string, unknown>>)[
547+
APPROVAL_CONTEXT_ID
548+
],
549+
resumeStatus: 'resuming',
550+
},
551+
},
552+
})
553+
.where(eq(pausedExecutions.id, pausedRow.id))
554+
await db
555+
.update(workflowExecutionLogs)
556+
.set({ status: 'running' })
557+
.where(eq(workflowExecutionLogs.executionId, executionId))
558+
559+
const completion = await waitForWorkflowToolCompletion({
560+
toolCallId,
561+
workflowId,
562+
timeoutMs: WAIT_MS,
563+
})
564+
565+
expect(completion).toMatchObject({
566+
status: 'background',
567+
data: { status: 'running', workflowId, executionId },
568+
})
569+
})
570+
533571
it("never hands a call the link to another call's paused run", async () => {
534572
const other = await claimedAndPausedUnreported('human')
535573
const { toolCallId } = await claimed()

0 commit comments

Comments
 (0)