fix: allow forking a chat that holds a stale unfinished message (#30131)

Forking returned 409 "Wait for the current response to finish before forking." forever once any assistant message anywhere in the chat was left at done = false, with nothing generating and even when that message sat on a branch that was not being forked. Interrupted turns leave the flag behind and nothing clears it, so an affected chat could never be forked again.

The endpoint now refuses only while a task is actually running, which is the check /compact has always relied on by itself. A message still unfinished on the forked branch is marked done in the copy so the fork does not open showing a spinner, while a turn paused waiting on tool approval keeps its unfinished state and its pending call so the fork still shows the prompt. The source chat is left untouched either way.

The client-side check had the same shape of bug: it read history.currentId instead of the message actually being forked, so a paused last turn also blocked forking earlier, finished messages. It now reads the message it is about to fork.

Fixes #30128
This commit is contained in:
Classic298 2026-09-19 01:31:20 +02:00 • committed by GitHub
parent fffcb727d2
commit 52cd298411
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 18 additions and 11 deletions

View file

@ -1690,15 +1690,6 @@ async def fork_chat_by_id(
history = (chat.chat or {}).get('history') or {}
messages_map = await Chats.get_messages_map_by_chat_id(id) or history.get('messages') or {}
if any(
message.get('role') == 'assistant' and message.get('done') is False
for message in messages_map.values()
if isinstance(message, dict)
):
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail='Wait for the current response to finish before forking.',
)
source_message_id = (
(form_data.message_id if form_data else None) or chat.current_message_id or history.get('currentId')
@ -1715,6 +1706,22 @@ async def fork_chat_by_id(
detail=detail,
) from exc
# An unfinished message is stale unless it is awaiting tool approval
for message in fork_history['messages'].values():
if message.get('role') != 'assistant' or message.get('done') is not False:
continue
output = message.get('output')
if isinstance(output, list) and any(
isinstance(item, dict)
and item.get('type') == 'function_call'
and item.get('status') in {'pending', 'queued', 'requires_approval'}
for item in output
):
continue
message['done'] = True
updated_chat = {**(chat.chat or {})}
updated_chat.pop('currentId', None)
updated_chat.update(

View file

@ -3035,11 +3035,11 @@
return;
}
const currentMessage = history.messages?.[history.currentId];
const forkedMessage = history.messages?.[messageId ?? history.currentId];
if (
generating ||
taskIds?.length ||
(currentMessage?.role === 'assistant' && !currentMessage.done)
(forkedMessage?.role === 'assistant' && !forkedMessage.done)
) {
toast.warning($i18n.t('Wait for the current response to finish before forking.'));
return;