[GH-ISSUE #23663] issue: Chat window freezes indefinitely when LLM returns ContentPolicyViolationError ( status: 400) #20035

Closed
opened 2026-04-20 02:37:04 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @tasawwuramd on GitHub (Apr 13, 2026).
Original GitHub issue: https://github.com/open-webui/open-webui/issues/23663

Check Existing Issues

  • I have searched for any existing and/or related issues.
  • I have searched for any existing and/or related discussions.
  • I have also searched in the CLOSED issues AND CLOSED discussions and found no related items (your issue might already be addressed on the development branch!).
  • I am using the latest version of Open WebUI.

Installation Method

Git Clone

Open WebUI Version

0.8.12

Ollama Version (if applicable)

No response

Operating System

Linux

Browser (if applicable)

Chrome: 146.0 , Edge: 146.0.

Confirmation

  • I have read and followed all instructions in README.md.
  • I am using the latest version of both Open WebUI and Ollama.
  • I have included the browser console logs.
  • I have included the Docker container logs.
  • I have provided every relevant configuration, setting, and environment variable used in my setup.
  • I have clearly listed every relevant configuration, custom setting, environment variable, and command-line option that influences my setup (such as Docker Compose overrides, .env values, browser settings, authentication configurations, etc).
  • I have documented step-by-step reproduction instructions that are precise, sequential, and leave nothing to interpretation. My steps:
  • Start with the initial platform/version/OS and dependencies used,
  • Specify exact install/launch/configure commands,
  • List URLs visited, user input (incl. example values/emails/passwords if needed),
  • Describe all options and toggles enabled or changed,
  • Include any files or environmental changes,
  • Identify the expected and actual result at each stage,
  • Ensure any reasonably skilled user can follow and hit the same issue.

Expected Behavior

The chat window exits the loading state and displays an error message to the user (e.g. "The response was filtered due to the content management policy").

Actual Behavior

The chat window freezes. The loading spinner spins indefinitely. taskIds is never cleared. The user must reload the page to recover.

Steps to Reproduce

  1. Configure an OpenAI-compatible connection backed by Azure OpenAI with content filtering enabled.
  2. Send a chat message that triggers Azure's content management policy.
  3. Http 400 status code generated
  4. Observe the chat window — it hangs indefinitely with the loading spinner.
  5. Reload the page — the error bubble becomes visible on the message, confirming the error was received but the UI was never unblocked

The error is reproducible with any upstream that returns HTTP 4xx with a JSON body (Azure content filter, guardrails, quota exceeded, etc.). Any non-2xx non-streaming response from the LLM backend will trigger this freeze.

  • The bug only affects the task-based async path (when session_id, chat_id, and message_id are all present in metadata). Direct / legacy sync calls are not affected.
  • The stored error is visible after a page reload, confirming the data reaches the DB correctly — only the real-time UI signal is missing.

Logs & Screenshots

Logs:
open-webui[607740]: └ ValueError(<ERROR_MESSAGES.EMPTY_CONTENT: 'The content provided is empty. Please ensure that there is text or data present be...
open-webui[607740]: raise ValueError(ERROR_MESSAGES.EMPTY_CONTENT)

Additional Information

The bug lives in two places in the backend pipeline:

1. backend/open_webui/routers/openai.pygenerate_chat_completion

When the upstream LLM returns HTTP 400, the function parses the JSON body and
returns a JSONResponse(status_code=400, ...) instead of raising an exception:

if r.status >= 400:
    if isinstance(response, (dict, list)):
        return JSONResponse(status_code=r.status, content=response)                                                                                            
 
2. backend/open_webui/utils/middleware.py  non_streaming_chat_response_handler                                                                                
                
Because no exception is raised, the except block in main.py:process_chat                                                                                       
(which emits chat:tasks:cancel) is never reached. The handler receives the
JSONResponse(400), correctly emits chat:message:error via the socket, but                                                                                      
then returns without emitting any finalising event:                                                                                                            
                                                                                                                                                               
# error branch — emits chat:message:error, then falls through silently                                                                                         
if isinstance(error, str) or isinstance(error, dict):                                                                                                          
    await event_emitter({
        'type': 'chat:message:error',                                                                                                                          
        'data': {'error': {'content': error}},                                                                                                                 
    })
# ← no chat:tasks:cancel, no chat:completion{done:true} emitted here                                                                                           
                                                                                                                                                               
On the frontend (Chat.svelte), the chat:message:error handler sets                                                                                             
message.error but does not set message.done = true and does not clear                                                                                          
taskIds. Only chat:tasks:cancel or chat:completion{done: true} unblocks                                                                                        
the UI. Neither is ever sent.

**Proposed Fix**    
                                                                                                                                                               
In non_streaming_chat_response_handler, emit chat:tasks:cancel immediately                                                                                     
after chat:message:error so the frontend exits the loading state:
                                                                                                                                                               
# backend/open_webui/utils/middleware.py  (~line 3077)
if isinstance(error, str) or isinstance(error, dict):                                                                                                          
    await event_emitter(
        {                                                                                                                                                      
            'type': 'chat:message:error',
            'data': {'error': {'content': error}},
        }                                                                                                                                                      
    )
    await event_emitter({'type': 'chat:tasks:cancel'})  # ← add this line                                                                                      
                
This reuses the existing chat:tasks:cancel frontend handler                                                                                                    
(Chat.svelte:435) which already clears taskIds, sets all sibling response
messages to done = true, and calls processNextInQueue  matching the                                                                                           
behaviour when a task is explicitly cancelled. 
Originally created by @tasawwuramd on GitHub (Apr 13, 2026). Original GitHub issue: https://github.com/open-webui/open-webui/issues/23663 ### Check Existing Issues - [x] I have searched for any existing and/or related issues. - [x] I have searched for any existing and/or related discussions. - [x] I have also searched in the CLOSED issues AND CLOSED discussions and found no related items (your issue might already be addressed on the development branch!). - [x] I am using the latest version of Open WebUI. ### Installation Method Git Clone ### Open WebUI Version 0.8.12 ### Ollama Version (if applicable) _No response_ ### Operating System Linux ### Browser (if applicable) Chrome: 146.0 , Edge: 146.0. ### Confirmation - [x] I have read and followed all instructions in `README.md`. - [x] I am using the latest version of **both** Open WebUI and Ollama. - [x] I have included the browser console logs. - [x] I have included the Docker container logs. - [x] I have **provided every relevant configuration, setting, and environment variable used in my setup.** - [x] I have clearly **listed every relevant configuration, custom setting, environment variable, and command-line option that influences my setup** (such as Docker Compose overrides, .env values, browser settings, authentication configurations, etc). - [x] I have documented **step-by-step reproduction instructions that are precise, sequential, and leave nothing to interpretation**. My steps: - Start with the initial platform/version/OS and dependencies used, - Specify exact install/launch/configure commands, - List URLs visited, user input (incl. example values/emails/passwords if needed), - Describe all options and toggles enabled or changed, - Include any files or environmental changes, - Identify the expected and actual result at each stage, - Ensure any reasonably skilled user can follow and hit the same issue. ### Expected Behavior The chat window exits the loading state and displays an error message to the user (e.g. "The response was filtered due to the content management policy"). ### Actual Behavior The chat window freezes. The loading spinner spins indefinitely. `taskIds` is never cleared. The user must reload the page to recover. ### Steps to Reproduce 1. Configure an OpenAI-compatible connection backed by Azure OpenAI with content filtering enabled. 2. Send a chat message that triggers Azure's content management policy. 3. Http 400 status code generated 4. Observe the chat window — it hangs indefinitely with the loading spinner. 5. Reload the page — the error bubble becomes visible on the message, confirming the error was received but the UI was never unblocked The error is reproducible with any upstream that returns HTTP 4xx with a JSON body (Azure content filter, guardrails, quota exceeded, etc.). Any non-2xx non-streaming response from the LLM backend will trigger this freeze. - The bug only affects the task-based async path (when session_id, chat_id, and message_id are all present in metadata). Direct / legacy sync calls are not affected. - The stored error is visible after a page reload, confirming the data reaches the DB correctly — only the real-time UI signal is missing. ### Logs & Screenshots Logs: open-webui[607740]: └ ValueError(<ERROR_MESSAGES.EMPTY_CONTENT: 'The content provided is empty. Please ensure that there is text or data present be... open-webui[607740]: raise ValueError(ERROR_MESSAGES.EMPTY_CONTENT) ### Additional Information The bug lives in two places in the backend pipeline: **1. `backend/open_webui/routers/openai.py` — `generate_chat_completion`** When the upstream LLM returns HTTP 400, the function parses the JSON body and returns a `JSONResponse(status_code=400, ...)` instead of raising an exception: ```python if r.status >= 400: if isinstance(response, (dict, list)): return JSONResponse(status_code=r.status, content=response) 2. backend/open_webui/utils/middleware.py — non_streaming_chat_response_handler Because no exception is raised, the except block in main.py:process_chat (which emits chat:tasks:cancel) is never reached. The handler receives the JSONResponse(400), correctly emits chat:message:error via the socket, but then returns without emitting any finalising event: # error branch — emits chat:message:error, then falls through silently if isinstance(error, str) or isinstance(error, dict): await event_emitter({ 'type': 'chat:message:error', 'data': {'error': {'content': error}}, }) # ← no chat:tasks:cancel, no chat:completion{done:true} emitted here On the frontend (Chat.svelte), the chat:message:error handler sets message.error but does not set message.done = true and does not clear taskIds. Only chat:tasks:cancel or chat:completion{done: true} unblocks the UI. Neither is ever sent. **Proposed Fix** In non_streaming_chat_response_handler, emit chat:tasks:cancel immediately after chat:message:error so the frontend exits the loading state: # backend/open_webui/utils/middleware.py (~line 3077) if isinstance(error, str) or isinstance(error, dict): await event_emitter( { 'type': 'chat:message:error', 'data': {'error': {'content': error}}, } ) await event_emitter({'type': 'chat:tasks:cancel'}) # ← add this line This reuses the existing chat:tasks:cancel frontend handler (Chat.svelte:435) which already clears taskIds, sets all sibling response messages to done = true, and calls processNextInQueue — matching the behaviour when a task is explicitly cancelled.
GiteaMirror added the bug label 2026-04-20 02:37:04 -05:00
Author
Owner

@tjbck commented on GitHub (Apr 13, 2026):

Should be addressed in dev.

<!-- gh-comment-id:4239008658 --> @tjbck commented on GitHub (Apr 13, 2026): Should be addressed in dev.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/open-webui#20035