Skip to content

Commit 1052bfd

Browse files
committed
fix(mothership): a declined call stays unclaimable without its gate marker
Calls gated before permission_requested_at existed carry no marker, so a recorded decision that does not allow the call now disqualifies it too.
1 parent 2c923ff commit 1052bfd

3 files changed

Lines changed: 26 additions & 3 deletions

File tree

‎apps/sim/lib/mothership/async-runs/lifecycle.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,17 @@ export function isExecutableToolPermissionDecision(
102102
return decision !== null && decision !== undefined && decision !== 'skip'
103103
}
104104

105-
/** A call held for the user's decision may run only once they allowed it. */
105+
/**
106+
* A call held for the user's decision may run only once they allowed it. A recorded decision
107+
* that does not allow it disqualifies the call even without the gate marker, which calls gated
108+
* before the marker existed lack.
109+
*/
106110
export function isAwaitingToolPermission(call: {
107111
permissionRequestedAt: Date | null
108112
permissionDecision: CopilotToolPermissionDecision | null
109113
}): boolean {
110114
return (
111-
Boolean(call.permissionRequestedAt) &&
115+
Boolean(call.permissionRequestedAt || call.permissionDecision) &&
112116
!isExecutableToolPermissionDecision(call.permissionDecision)
113117
)
114118
}

‎apps/sim/lib/mothership/async-runs/repository.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,10 @@ export async function claimToolExecution(
728728
thisCall,
729729
eq(copilotAsyncToolCalls.status, ASYNC_TOOL_STATUS.pending),
730730
or(
731-
isNull(copilotAsyncToolCalls.permissionRequestedAt),
731+
and(
732+
isNull(copilotAsyncToolCalls.permissionRequestedAt),
733+
isNull(copilotAsyncToolCalls.permissionDecision)
734+
),
732735
inArray(copilotAsyncToolCalls.permissionDecision, [
733736
...EXECUTABLE_TOOL_PERMISSION_DECISIONS,
734737
])

‎apps/sim/lib/mothership/tools/client/desktop-tool-authorization.integration.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,22 @@ describe.runIf(Boolean(redisUrl))('desktop tool calls the server no longer admit
260260
expect(await storedCall(toolCallId)).toMatchObject({ status: 'pending', claimedBy: null })
261261
})
262262

263+
it('refuses a call the user declined before its gate was recorded on the row', async () => {
264+
const { runId } = await startRun()
265+
const toolCallId = generateId()
266+
await db.insert(copilotAsyncToolCalls).values({
267+
runId,
268+
toolCallId,
269+
toolName: 'terminal',
270+
args: { operation: 'run', args: { command: 'git push --force' } },
271+
status: 'pending',
272+
permissionDecision: 'skip',
273+
})
274+
275+
expect((await desktopClaims(toolCallId)).status).toBe(403)
276+
expect(await storedCall(toolCallId)).toMatchObject({ status: 'pending', claimedBy: null })
277+
})
278+
263279
it('refuses a pending desktop call after Stop, and settles it as never started', async () => {
264280
const { runId, streamId } = await startRun()
265281
const toolCallId = await agentCalls(runId, 'browser_click', { ref: 'e12' })

0 commit comments

Comments
 (0)