Skip to content

Commit 5f3838a

Browse files
committed
fix(mothership): answer a browser run still running only once its log exists, and never discard a run that settled meanwhile
1 parent bdaf8c6 commit 5f3838a

3 files changed

Lines changed: 13 additions & 2 deletions

File tree

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
createStructuralWorkflowToolCompletionData,
2525
getWorkflowToolCompletionMessage,
2626
} from '@/lib/mothership/tools/workflow-tools'
27+
import { getWorkflowExecutionLogStatus } from '@/lib/workflows/executor/execution-state'
2728
import type { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
2829

2930
const logger = createLogger('CopilotWorkflowClientFallback')
@@ -67,7 +68,8 @@ const CLIENT_CLAIM_POLL_MS = 5000
6768
* Once `waitTimeoutMs` has passed, settles a browser-run workflow call as running in the
6869
* background, the same detach a browser sends on `pagehide`: its waiter answers the call with
6970
* the run's ids and the browser's run goes on, bound to the call, so a Stop still cancels it.
70-
* A call no browser has claimed yet is re-read until one does or the wait ends.
71+
* A call no browser has claimed yet, or whose run has no log yet, is re-read until it does or the
72+
* wait ends.
7173
*/
7274
async function detachClientRunAfterWait(params: {
7375
toolCallId: string
@@ -86,6 +88,11 @@ async function detachClientRunAfterWait(params: {
8688
if (!row || row.status !== ASYNC_TOOL_STATUS.running) return
8789
const executionId = getClaimedWorkflowExecutionId(row.claimedBy)
8890
if (!executionId || row.clientWorkflowExecutionId !== executionId) continue
91+
// The claim precedes the run's log; answer running only once the run is readable by its id.
92+
const logged = await getWorkflowExecutionLogStatus(executionId, workflowId).catch(
93+
() => undefined
94+
)
95+
if (!logged) continue
8996
const status = ASYNC_TOOL_CONFIRMATION_STATUS.background
9097
await settleClientToolCall({
9198
toolCallId,

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ describe.runIf(Boolean(redisUrl))('settled client-claimed workflow tools', () =>
156156
async function executionLog(
157157
toolCallId: string,
158158
executionId: string,
159-
status: 'completed' | 'failed' | 'cancelled' | 'pending'
159+
status: 'completed' | 'failed' | 'cancelled' | 'pending' | 'running'
160160
) {
161161
const snapshotId = generateId()
162162
snapshotIds.push(snapshotId)
@@ -496,6 +496,8 @@ describe.runIf(Boolean(redisUrl))('settled client-claimed workflow tools', () =>
496496

497497
it("settles a browser run that outlasts the worker's wait as running, bound to the call", async () => {
498498
const { toolCallId, executionId } = await claimed()
499+
// The execute route's log of the browser's run, still going: what the worker waits on.
500+
await executionLog(toolCallId, executionId, 'running')
499501
const runOnServer = vi.fn()
500502

501503
const outcome = await raceWorkflowToolClientPickup({

‎apps/sim/lib/mothership/tools/handlers/workflow/mutations.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,8 @@ async function answerWithinWait(
301301
while (!result && !(await runIsReadable(executionId, workflowId))) {
302302
result = await Promise.race([settled, waited(RUN_LOG_POLL_MS)])
303303
}
304+
// A run that settled while its log was being read answers with its result.
305+
result ??= await Promise.race([settled, Promise.resolve(null)])
304306
if (result) return result
305307
} finally {
306308
stopWaiting.abort()

0 commit comments

Comments
 (0)