fix: approving a tool call twice at once runs the tool twice

Double-clicking Approve on a tool call that waits for approval, or approving it from two open tabs, could run the tool twice while the chat only showed one result. Both approvals were accepted because neither saw the other. Now only the first approval goes through and the other gets the "already resolved" error, also across workers when Redis is configured. Approving two different tool calls in the same reply at the same time could also lose one approval, so one tool ran twice and the other never ran; both now run once.
This commit is contained in:
Classic298 2026-09-30 00:14:43 +02:00
parent 3819dc2793
commit 42fd727a4c
2 changed files with 16 additions and 4 deletions

View file

@ -1888,6 +1888,9 @@ generate_chat_completions = chat_completion
generate_chat_completion = chat_completion
_tool_call_resolve_locks: dict[str, asyncio.Lock] = {}
@app.post('/api/v1/chats/{id}/messages/{message_id}/resolve')
async def resolve_chat_message_tool_call(
request: Request,
@ -1897,7 +1900,14 @@ async def resolve_chat_message_tool_call(
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
resolution = await resolve_tool_call_output(id, message_id, form_data, user, db=db)
redis = request.app.state.redis
if redis:
resolve_lock = redis.lock(f'{REDIS_KEY_PREFIX}:tool_call:resolve_lock:{message_id}', timeout=60)
else:
resolve_lock = _tool_call_resolve_locks.setdefault(message_id, asyncio.Lock())
async with resolve_lock:
resolution = await resolve_tool_call_output(id, message_id, form_data, user, db=db)
payload = await build_tool_approval_resume_payload(id, message_id, chat=resolution['chat'])
result = await chat_completion(request, payload, user)
return {

View file

@ -55,9 +55,11 @@ async def resolve_tool_call_output(
function_call.setdefault('call_id', form_data.call_id)
tool_name = function_call.get('name')
if any(
item.get('type') == 'function_call_output' and item.get('call_id') == form_data.call_id for item in output
) or function_call.get('status') not in {'pending', 'queued', 'requires_approval'}:
if (
any(item.get('type') == 'function_call_output' and item.get('call_id') == form_data.call_id for item in output)
or function_call.get('status') not in {'pending', 'queued', 'requires_approval'}
or function_call.get('approved') is True
):
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail='Tool call has already been resolved.')
if form_data.action == 'approve':