[GH-ISSUE #15616] issue: PostgreSQL null bytes #56284

Closed
opened 2026-05-05 19:04:58 -05:00 by GiteaMirror · 32 comments
Owner

Originally created by @perelin on GitHub (Jul 9, 2025).
Original GitHub issue: https://github.com/open-webui/open-webui/issues/15616

Originally assigned to: @Classic298 on GitHub.

Check Existing Issues

  • I have searched the existing issues and discussions.
  • I am using the latest version of Open WebUI.

Installation Method

Docker

Open WebUI Version

v0.6.15

Ollama Version (if applicable)

No response

Operating System

macOS

Browser (if applicable)

Chrome

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

Disclaimer
Bug report writeup supported by AI

Expected Behavior
Chat search functionality should work seamlessly when users search through their chat messages via the /api/v1/chats/search endpoint.
chats.py:160-186 The search should process JSON data in the chat column without errors and return relevant chat results.

Actual Behavior

Chat search returns HTTP 500 error with psycopg2.errors.UntranslatableCharacter: unsupported Unicode escape sequence when PostgreSQL encounters null bytes (\u0000) in JSON chat data. The error specifically occurs during PostgreSQL JSON processing: chats.py:671-680

Steps to Reproduce

Detailed Steps:

  • Set up Open WebUI with PostgreSQL backend
  • Create or import chat messages containing null bytes (\u0000) in the message content
  • Navigate to the chat interface in your browser
  • Use the search functionality to search through your chats
  • Enter any search term (e.g., "semax23") in the chat search box
  • The search triggers a GET request to /api/v1/chats/search?text=<search_term>&page=1
  • Backend executes get_chats_by_user_id_and_search_text() method chats.py:572-723
  • PostgreSQL attempts to process the JSON query but fails on null bytes
  • Observe HTTP 500 error in browser and server logs

Logs & Screenshots

Docker Logs

Jul 09 23:22:56 2025-07-09 21:22:56.863 | INFO | uvicorn.protocols.http.httptools_impl:send:476 - 2a02:8071:185:b540:d97e:ecc:dbcf:f74e:0 - "GET /api/v1/chats/search?text=semax&page=1 HTTP/1.1" 500 - {}
Jul 09 23:22:56 Exception in ASGI application
Jul 09 23:22:56 Traceback (most recent call last):
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1964, in _exec_single_context
Jul 09 23:22:56 self.dialect.do_execute(
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 942, in do_execute
Jul 09 23:22:56 cursor.execute(statement, parameters)
Jul 09 23:22:56 psycopg2.errors.UntranslatableCharacter: unsupported Unicode escape sequence
Jul 09 23:22:56 DETAIL: \u0000 cannot be converted to text.
Jul 09 23:22:56 CONTEXT: JSON data, line 1: ....g., classification models). Some early \u0000...
Jul 09 23:22:56 2025-07-09T21:22:56Z
Jul 09 23:22:56 2025-07-09T21:22:56Z
Jul 09 23:22:56 The above exception was the direct cause of the following exception:
Jul 09 23:22:56 2025-07-09T21:22:56Z
Jul 09 23:22:56 Traceback (most recent call last):
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/uvicorn/protocols/http/httptools_impl.py", line 409, in run_asgi
Jul 09 23:22:56 result = await app( # type: ignore[func-returns-value]
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__
Jul 09 23:22:56 return await self.app(scope, receive, send)
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__
Jul 09 23:22:56 await super().__call__(scope, receive, send)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/applications.py", line 112, in __call__
Jul 09 23:22:56 await self.middleware_stack(scope, receive, send)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 187, in __call__
Jul 09 23:22:56 raise exc
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 165, in __call__
Jul 09 23:22:56 await self.app(scope, receive, _send)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/sessions.py", line 85, in __call__
Jul 09 23:22:56 await self.app(scope, receive, send_wrapper)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/cors.py", line 85, in __call__
Jul 09 23:22:56 await self.app(scope, receive, send)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__
Jul 09 23:22:56 with recv_stream, send_stream, collapse_excgroups():
Jul 09 23:22:56 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__
Jul 09 23:22:56 self.gen.throw(value)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups
Jul 09 23:22:56 raise exc
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__
Jul 09 23:22:56 response = await self.dispatch_func(request, call_next)
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/backend/open_webui/main.py", line 1117, in inspect_websocket
Jul 09 23:22:56 return await call_next(request)
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next
Jul 09 23:22:56 raise app_exc
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro
Jul 09 23:22:56 await self.app(scope, receive_or_disconnect, send_no_error)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__
Jul 09 23:22:56 with recv_stream, send_stream, collapse_excgroups():
Jul 09 23:22:56 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__
Jul 09 23:22:56 self.gen.throw(value)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups
Jul 09 23:22:56 raise exc
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__
Jul 09 23:22:56 response = await self.dispatch_func(request, call_next)
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/backend/open_webui/main.py", line 1096, in check_url
Jul 09 23:22:56 response = await call_next(request)
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next
Jul 09 23:22:56 raise app_exc
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro
Jul 09 23:22:56 await self.app(scope, receive_or_disconnect, send_no_error)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__
Jul 09 23:22:56 with recv_stream, send_stream, collapse_excgroups():
Jul 09 23:22:56 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__
Jul 09 23:22:56 self.gen.throw(value)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups
Jul 09 23:22:56 raise exc
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__
Jul 09 23:22:56 response = await self.dispatch_func(request, call_next)
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/backend/open_webui/main.py", line 1082, in commit_session_after_request
Jul 09 23:22:56 response = await call_next(request)
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next
Jul 09 23:22:56 raise app_exc
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro
Jul 09 23:22:56 await self.app(scope, receive_or_disconnect, send_no_error)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__
Jul 09 23:22:56 with recv_stream, send_stream, collapse_excgroups():
Jul 09 23:22:56 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__
Jul 09 23:22:56 self.gen.throw(value)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups
Jul 09 23:22:56 raise exc
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__
Jul 09 23:22:56 response = await self.dispatch_func(request, call_next)
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/backend/open_webui/utils/security_headers.py", line 11, in dispatch
Jul 09 23:22:56 response = await call_next(request)
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next
Jul 09 23:22:56 raise app_exc
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro
Jul 09 23:22:56 await self.app(scope, receive_or_disconnect, send_no_error)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__
Jul 09 23:22:56 with recv_stream, send_stream, collapse_excgroups():
Jul 09 23:22:56 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__
Jul 09 23:22:56 self.gen.throw(value)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups
Jul 09 23:22:56 raise exc
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__
Jul 09 23:22:56 response = await self.dispatch_func(request, call_next)
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/backend/open_webui/main.py", line 1070, in dispatch
Jul 09 23:22:56 response = await call_next(request)
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next
Jul 09 23:22:56 raise app_exc
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro
Jul 09 23:22:56 await self.app(scope, receive_or_disconnect, send_no_error)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette_compress/__init__.py", line 92, in __call__
Jul 09 23:22:56 return await self._zstd(scope, receive, send)
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette_compress/_zstd_legacy.py", line 100, in __call__
Jul 09 23:22:56 await self.app(scope, receive, wrapper)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 62, in __call__
Jul 09 23:22:56 await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
Jul 09 23:22:56 raise exc
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
Jul 09 23:22:56 await app(scope, receive, sender)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 715, in __call__
Jul 09 23:22:56 await self.middleware_stack(scope, receive, send)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 735, in app
Jul 09 23:22:56 await route.handle(scope, receive, send)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 288, in handle
Jul 09 23:22:56 await self.app(scope, receive, send)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 76, in app
Jul 09 23:22:56 await wrap_app_handling_exceptions(app, request)(scope, receive, send)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
Jul 09 23:22:56 raise exc
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
Jul 09 23:22:56 await app(scope, receive, sender)
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 73, in app
Jul 09 23:22:56 response = await f(request)
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/fastapi/routing.py", line 301, in app
Jul 09 23:22:56 raw_response = await run_endpoint_function(
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/fastapi/routing.py", line 212, in run_endpoint_function
Jul 09 23:22:56 return await dependant.call(**values)
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/backend/open_webui/routers/chats.py", line 172, in search_user_chats
Jul 09 23:22:56 for chat in Chats.get_chats_by_user_id_and_search_text(
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/backend/open_webui/models/chats.py", line 718, in get_chats_by_user_id_and_search_text
Jul 09 23:22:56 all_chats = query.offset(skip).limit(limit).all()
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/query.py", line 2699, in all
Jul 09 23:22:56 return self._iter().all() # type: ignore
Jul 09 23:22:56 ^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/query.py", line 2853, in _iter
Jul 09 23:22:56 result: Union[ScalarResult[_T], Result[_T]] = self.session.execute(
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2365, in execute
Jul 09 23:22:56 return self._execute_internal(
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2251, in _execute_internal
Jul 09 23:22:56 result: Result[Any] = compile_state_cls.orm_execute_statement(
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/context.py", line 305, in orm_execute_statement
Jul 09 23:22:56 result = conn.execute(
Jul 09 23:22:56 ^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
Jul 09 23:22:56 return meth(
Jul 09 23:22:56 ^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection
Jul 09 23:22:56 return connection._execute_clauseelement(
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1638, in _execute_clauseelement
Jul 09 23:22:56 ret = self._execute_context(
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1843, in _execute_context
Jul 09 23:22:56 return self._exec_single_context(
Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1983, in _exec_single_context
Jul 09 23:22:56 self._handle_dbapi_exception(
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 2352, in _handle_dbapi_exception
Jul 09 23:22:56 raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1964, in _exec_single_context
Jul 09 23:22:56 self.dialect.do_execute(
Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 942, in do_execute
Jul 09 23:22:56 cursor.execute(statement, parameters)
Jul 09 23:22:56 sqlalchemy.exc.DataError: (psycopg2.errors.UntranslatableCharacter) unsupported Unicode escape sequence
Jul 09 23:22:56 DETAIL: \u0000 cannot be converted to text.
Jul 09 23:22:56 CONTEXT: JSON data, line 1: ....g., classification models). Some early \u0000...
Jul 09 23:22:56 2025-07-09T21:22:56Z
Jul 09 23:22:56 [SQL: SELECT chat.id AS chat_id, chat.user_id AS chat_user_id, chat.title AS chat_title, chat.chat AS chat_chat, chat.created_at AS chat_created_at, chat.updated_at AS chat_updated_at, chat.share_id AS chat_share_id, chat.archived AS chat_archived, chat.pinned AS chat_pinned, chat.meta AS chat_meta, chat.folder_id AS chat_folder_id
Jul 09 23:22:56 FROM chat
Jul 09 23:22:56 WHERE chat.user_id = %(user_id_1)s AND chat.archived = false AND (chat.title ILIKE %(title_1)s OR
Jul 09 23:22:56 EXISTS (
Jul 09 23:22:56 SELECT 1
Jul 09 23:22:56 FROM json_array_elements(Chat.chat->'messages') AS message
Jul 09 23:22:56 WHERE LOWER(message->>'content') LIKE '%%' || %(search_text)s || '%%'
Jul 09 23:22:56 )
Jul 09 23:22:56 ) ORDER BY chat.updated_at DESC
Jul 09 23:22:56 LIMIT %(param_1)s OFFSET %(param_2)s]
Jul 09 23:22:56 [parameters: {'user_id_1': '2973a326-4694-414a-ac91-52c401d9823b', 'title_1': '%semax%', 'search_text': 'semax', 'param_1': 60, 'param_2': 0}]
Jul 09 23:22:56 (Background on this error at: https://sqlalche.me/e/20/9h9h)

Browser console Logs

index.ts:255  GET https://openwebui.p2lab.com/api/v1/chats/search?text=semaxc&page=1 500 (Internal Server Error)
window.fetch @ fetcher.js:76
T @ index.ts:255
(anonymous) @ SearchModal.svelte:42
setTimeout
S @ SearchModal.svelte:41
(anonymous) @ lifecycle.js:105
(anonymous) @ lifecycle.js:104
y @ SearchInput.svelte:113
index.ts:272 SyntaxError: Unexpected token 'I', "Internal S"... is not valid JSON
(anonymous) @ index.ts:272
Promise.catch
T @ index.ts:270
(anonymous) @ SearchModal.svelte:42
setTimeout
S @ SearchModal.svelte:41
(anonymous) @ lifecycle.js:105
(anonymous) @ lifecycle.js:104
y @ SearchInput.svelte:113
VM1092:1 Uncaught (in promise) SyntaxError: Unexpected token 'I', "Internal S"... is not valid JSON
index.ts:255  GET https://openwebui.p2lab.com/api/v1/chats/search?text=semax&page=1 500 (Internal Server Error)
window.fetch @ fetcher.js:76
T @ index.ts:255
(anonymous) @ SearchModal.svelte:42
setTimeout
S @ SearchModal.svelte:41
(anonymous) @ lifecycle.js:105
(anonymous) @ lifecycle.js:104
y @ SearchInput.svelte:113
index.ts:272 SyntaxError: Unexpected token 'I', "Internal S"... is not valid JSON
(anonymous) @ index.ts:272
Promise.catch
T @ index.ts:270
(anonymous) @ SearchModal.svelte:42
setTimeout
S @ SearchModal.svelte:41
(anonymous) @ lifecycle.js:105
(anonymous) @ lifecycle.js:104
y @ SearchInput.svelte:113
VM1095:1 Uncaught (in promise) SyntaxError: Unexpected token 'I', "Internal S"... is not valid JSON

Additional Information

Root Cause:
The issue stems from the database migration 242a2047eae0_update_chat_table.py:64-74 that converted the chat column from Text to JSON format. During this migration, text data containing null bytes was converted to JSON, but PostgreSQL's JSON functions cannot handle these characters when performing searches.

Impact:

  • Affects PostgreSQL deployments specifically
  • SQLite deployments handle this differently and may not exhibit the same issue
  • The error occurs during JSON array element processing in search queries
  • No current UI exists for data cleaning operations

Proposed Solutions:

  • Add data sanitization during migration to strip null bytes
  • Implement input validation to prevent null bytes in chat content
  • Add graceful error handling for PostgreSQL JSON processing failures
  • Provide UI or admin tools for identifying and cleaning corrupted chat data
Originally created by @perelin on GitHub (Jul 9, 2025). Original GitHub issue: https://github.com/open-webui/open-webui/issues/15616 Originally assigned to: @Classic298 on GitHub. ### Check Existing Issues - [x] I have searched the existing issues and discussions. - [x] I am using the latest version of Open WebUI. ### Installation Method Docker ### Open WebUI Version v0.6.15 ### Ollama Version (if applicable) _No response_ ### Operating System macOS ### Browser (if applicable) Chrome ### 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 **Disclaimer** Bug report writeup supported by AI **Expected Behavior** Chat search functionality should work seamlessly when users search through their chat messages via the /api/v1/chats/search endpoint. `chats.py:160-186` The search should process JSON data in the chat column without errors and return relevant chat results. ### Actual Behavior Chat search returns HTTP 500 error with psycopg2.errors.UntranslatableCharacter: unsupported Unicode escape sequence when PostgreSQL encounters null bytes (\u0000) in JSON chat data. The error specifically occurs during PostgreSQL JSON processing: `chats.py:671-680` ### Steps to Reproduce Detailed Steps: - Set up Open WebUI with PostgreSQL backend - Create or import chat messages containing null bytes (`\u0000`) in the message content - Navigate to the chat interface in your browser - Use the search functionality to search through your chats - Enter any search term (e.g., "semax23") in the chat search box - The search triggers a GET request to `/api/v1/chats/search?text=<search_term>&page=1` - Backend executes `get_chats_by_user_id_and_search_text()` method `chats.py:572-723` - PostgreSQL attempts to process the JSON query but fails on null bytes - Observe HTTP 500 error in browser and server logs ### Logs & Screenshots Docker Logs ``` Jul 09 23:22:56 2025-07-09 21:22:56.863 | INFO | uvicorn.protocols.http.httptools_impl:send:476 - 2a02:8071:185:b540:d97e:ecc:dbcf:f74e:0 - "GET /api/v1/chats/search?text=semax&page=1 HTTP/1.1" 500 - {} Jul 09 23:22:56 Exception in ASGI application Jul 09 23:22:56 Traceback (most recent call last): Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1964, in _exec_single_context Jul 09 23:22:56 self.dialect.do_execute( Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 942, in do_execute Jul 09 23:22:56 cursor.execute(statement, parameters) Jul 09 23:22:56 psycopg2.errors.UntranslatableCharacter: unsupported Unicode escape sequence Jul 09 23:22:56 DETAIL: \u0000 cannot be converted to text. Jul 09 23:22:56 CONTEXT: JSON data, line 1: ....g., classification models). Some early \u0000... Jul 09 23:22:56 2025-07-09T21:22:56Z Jul 09 23:22:56 2025-07-09T21:22:56Z Jul 09 23:22:56 The above exception was the direct cause of the following exception: Jul 09 23:22:56 2025-07-09T21:22:56Z Jul 09 23:22:56 Traceback (most recent call last): Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/uvicorn/protocols/http/httptools_impl.py", line 409, in run_asgi Jul 09 23:22:56 result = await app( # type: ignore[func-returns-value] Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__ Jul 09 23:22:56 return await self.app(scope, receive, send) Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__ Jul 09 23:22:56 await super().__call__(scope, receive, send) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/applications.py", line 112, in __call__ Jul 09 23:22:56 await self.middleware_stack(scope, receive, send) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 187, in __call__ Jul 09 23:22:56 raise exc Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 165, in __call__ Jul 09 23:22:56 await self.app(scope, receive, _send) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/sessions.py", line 85, in __call__ Jul 09 23:22:56 await self.app(scope, receive, send_wrapper) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/cors.py", line 85, in __call__ Jul 09 23:22:56 await self.app(scope, receive, send) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__ Jul 09 23:22:56 with recv_stream, send_stream, collapse_excgroups(): Jul 09 23:22:56 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__ Jul 09 23:22:56 self.gen.throw(value) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups Jul 09 23:22:56 raise exc Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__ Jul 09 23:22:56 response = await self.dispatch_func(request, call_next) Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/backend/open_webui/main.py", line 1117, in inspect_websocket Jul 09 23:22:56 return await call_next(request) Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next Jul 09 23:22:56 raise app_exc Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro Jul 09 23:22:56 await self.app(scope, receive_or_disconnect, send_no_error) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__ Jul 09 23:22:56 with recv_stream, send_stream, collapse_excgroups(): Jul 09 23:22:56 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__ Jul 09 23:22:56 self.gen.throw(value) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups Jul 09 23:22:56 raise exc Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__ Jul 09 23:22:56 response = await self.dispatch_func(request, call_next) Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/backend/open_webui/main.py", line 1096, in check_url Jul 09 23:22:56 response = await call_next(request) Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next Jul 09 23:22:56 raise app_exc Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro Jul 09 23:22:56 await self.app(scope, receive_or_disconnect, send_no_error) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__ Jul 09 23:22:56 with recv_stream, send_stream, collapse_excgroups(): Jul 09 23:22:56 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__ Jul 09 23:22:56 self.gen.throw(value) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups Jul 09 23:22:56 raise exc Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__ Jul 09 23:22:56 response = await self.dispatch_func(request, call_next) Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/backend/open_webui/main.py", line 1082, in commit_session_after_request Jul 09 23:22:56 response = await call_next(request) Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next Jul 09 23:22:56 raise app_exc Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro Jul 09 23:22:56 await self.app(scope, receive_or_disconnect, send_no_error) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__ Jul 09 23:22:56 with recv_stream, send_stream, collapse_excgroups(): Jul 09 23:22:56 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__ Jul 09 23:22:56 self.gen.throw(value) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups Jul 09 23:22:56 raise exc Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__ Jul 09 23:22:56 response = await self.dispatch_func(request, call_next) Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/backend/open_webui/utils/security_headers.py", line 11, in dispatch Jul 09 23:22:56 response = await call_next(request) Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next Jul 09 23:22:56 raise app_exc Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro Jul 09 23:22:56 await self.app(scope, receive_or_disconnect, send_no_error) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__ Jul 09 23:22:56 with recv_stream, send_stream, collapse_excgroups(): Jul 09 23:22:56 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__ Jul 09 23:22:56 self.gen.throw(value) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups Jul 09 23:22:56 raise exc Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__ Jul 09 23:22:56 response = await self.dispatch_func(request, call_next) Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/backend/open_webui/main.py", line 1070, in dispatch Jul 09 23:22:56 response = await call_next(request) Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next Jul 09 23:22:56 raise app_exc Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro Jul 09 23:22:56 await self.app(scope, receive_or_disconnect, send_no_error) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette_compress/__init__.py", line 92, in __call__ Jul 09 23:22:56 return await self._zstd(scope, receive, send) Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette_compress/_zstd_legacy.py", line 100, in __call__ Jul 09 23:22:56 await self.app(scope, receive, wrapper) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 62, in __call__ Jul 09 23:22:56 await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app Jul 09 23:22:56 raise exc Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app Jul 09 23:22:56 await app(scope, receive, sender) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 715, in __call__ Jul 09 23:22:56 await self.middleware_stack(scope, receive, send) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 735, in app Jul 09 23:22:56 await route.handle(scope, receive, send) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 288, in handle Jul 09 23:22:56 await self.app(scope, receive, send) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 76, in app Jul 09 23:22:56 await wrap_app_handling_exceptions(app, request)(scope, receive, send) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app Jul 09 23:22:56 raise exc Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app Jul 09 23:22:56 await app(scope, receive, sender) Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 73, in app Jul 09 23:22:56 response = await f(request) Jul 09 23:22:56 ^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/fastapi/routing.py", line 301, in app Jul 09 23:22:56 raw_response = await run_endpoint_function( Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/fastapi/routing.py", line 212, in run_endpoint_function Jul 09 23:22:56 return await dependant.call(**values) Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/backend/open_webui/routers/chats.py", line 172, in search_user_chats Jul 09 23:22:56 for chat in Chats.get_chats_by_user_id_and_search_text( Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/backend/open_webui/models/chats.py", line 718, in get_chats_by_user_id_and_search_text Jul 09 23:22:56 all_chats = query.offset(skip).limit(limit).all() Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/query.py", line 2699, in all Jul 09 23:22:56 return self._iter().all() # type: ignore Jul 09 23:22:56 ^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/query.py", line 2853, in _iter Jul 09 23:22:56 result: Union[ScalarResult[_T], Result[_T]] = self.session.execute( Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2365, in execute Jul 09 23:22:56 return self._execute_internal( Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2251, in _execute_internal Jul 09 23:22:56 result: Result[Any] = compile_state_cls.orm_execute_statement( Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/context.py", line 305, in orm_execute_statement Jul 09 23:22:56 result = conn.execute( Jul 09 23:22:56 ^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1416, in execute Jul 09 23:22:56 return meth( Jul 09 23:22:56 ^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection Jul 09 23:22:56 return connection._execute_clauseelement( Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1638, in _execute_clauseelement Jul 09 23:22:56 ret = self._execute_context( Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1843, in _execute_context Jul 09 23:22:56 return self._exec_single_context( Jul 09 23:22:56 ^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1983, in _exec_single_context Jul 09 23:22:56 self._handle_dbapi_exception( Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 2352, in _handle_dbapi_exception Jul 09 23:22:56 raise sqlalchemy_exception.with_traceback(exc_info[2]) from e Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1964, in _exec_single_context Jul 09 23:22:56 self.dialect.do_execute( Jul 09 23:22:56 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 942, in do_execute Jul 09 23:22:56 cursor.execute(statement, parameters) Jul 09 23:22:56 sqlalchemy.exc.DataError: (psycopg2.errors.UntranslatableCharacter) unsupported Unicode escape sequence Jul 09 23:22:56 DETAIL: \u0000 cannot be converted to text. Jul 09 23:22:56 CONTEXT: JSON data, line 1: ....g., classification models). Some early \u0000... Jul 09 23:22:56 2025-07-09T21:22:56Z Jul 09 23:22:56 [SQL: SELECT chat.id AS chat_id, chat.user_id AS chat_user_id, chat.title AS chat_title, chat.chat AS chat_chat, chat.created_at AS chat_created_at, chat.updated_at AS chat_updated_at, chat.share_id AS chat_share_id, chat.archived AS chat_archived, chat.pinned AS chat_pinned, chat.meta AS chat_meta, chat.folder_id AS chat_folder_id Jul 09 23:22:56 FROM chat Jul 09 23:22:56 WHERE chat.user_id = %(user_id_1)s AND chat.archived = false AND (chat.title ILIKE %(title_1)s OR Jul 09 23:22:56 EXISTS ( Jul 09 23:22:56 SELECT 1 Jul 09 23:22:56 FROM json_array_elements(Chat.chat->'messages') AS message Jul 09 23:22:56 WHERE LOWER(message->>'content') LIKE '%%' || %(search_text)s || '%%' Jul 09 23:22:56 ) Jul 09 23:22:56 ) ORDER BY chat.updated_at DESC Jul 09 23:22:56 LIMIT %(param_1)s OFFSET %(param_2)s] Jul 09 23:22:56 [parameters: {'user_id_1': '2973a326-4694-414a-ac91-52c401d9823b', 'title_1': '%semax%', 'search_text': 'semax', 'param_1': 60, 'param_2': 0}] Jul 09 23:22:56 (Background on this error at: https://sqlalche.me/e/20/9h9h) ``` Browser console Logs ``` index.ts:255 GET https://openwebui.p2lab.com/api/v1/chats/search?text=semaxc&page=1 500 (Internal Server Error) window.fetch @ fetcher.js:76 T @ index.ts:255 (anonymous) @ SearchModal.svelte:42 setTimeout S @ SearchModal.svelte:41 (anonymous) @ lifecycle.js:105 (anonymous) @ lifecycle.js:104 y @ SearchInput.svelte:113 index.ts:272 SyntaxError: Unexpected token 'I', "Internal S"... is not valid JSON (anonymous) @ index.ts:272 Promise.catch T @ index.ts:270 (anonymous) @ SearchModal.svelte:42 setTimeout S @ SearchModal.svelte:41 (anonymous) @ lifecycle.js:105 (anonymous) @ lifecycle.js:104 y @ SearchInput.svelte:113 VM1092:1 Uncaught (in promise) SyntaxError: Unexpected token 'I', "Internal S"... is not valid JSON index.ts:255 GET https://openwebui.p2lab.com/api/v1/chats/search?text=semax&page=1 500 (Internal Server Error) window.fetch @ fetcher.js:76 T @ index.ts:255 (anonymous) @ SearchModal.svelte:42 setTimeout S @ SearchModal.svelte:41 (anonymous) @ lifecycle.js:105 (anonymous) @ lifecycle.js:104 y @ SearchInput.svelte:113 index.ts:272 SyntaxError: Unexpected token 'I', "Internal S"... is not valid JSON (anonymous) @ index.ts:272 Promise.catch T @ index.ts:270 (anonymous) @ SearchModal.svelte:42 setTimeout S @ SearchModal.svelte:41 (anonymous) @ lifecycle.js:105 (anonymous) @ lifecycle.js:104 y @ SearchInput.svelte:113 VM1095:1 Uncaught (in promise) SyntaxError: Unexpected token 'I', "Internal S"... is not valid JSON ``` ### Additional Information **Root Cause:** The issue stems from the database migration `242a2047eae0_update_chat_table.py:64-74` that converted the `chat` column from Text to JSON format. During this migration, text data containing null bytes was converted to JSON, but PostgreSQL's JSON functions cannot handle these characters when performing searches. **Impact:** - Affects PostgreSQL deployments specifically - SQLite deployments handle this differently and may not exhibit the same issue - The error occurs during JSON array element processing in search queries - No current UI exists for data cleaning operations **Proposed Solutions:** - Add data sanitization during migration to strip null bytes - Implement input validation to prevent null bytes in chat content - Add graceful error handling for PostgreSQL JSON processing failures - Provide UI or admin tools for identifying and cleaning corrupted chat data
GiteaMirror added the bug label 2026-05-05 19:04:58 -05:00
Author
Owner

@Ithanil commented on GitHub (Jul 10, 2025):

Same issue/discussion: https://github.com/open-webui/open-webui/discussions/9318
Should by fixed by: https://github.com/open-webui/open-webui/pull/14957

<!-- gh-comment-id:3057228923 --> @Ithanil commented on GitHub (Jul 10, 2025): Same issue/discussion: https://github.com/open-webui/open-webui/discussions/9318 Should by fixed by: https://github.com/open-webui/open-webui/pull/14957
Author
Owner

@perelin commented on GitHub (Jul 21, 2025):

@Ithanil thanks! is there a release scheduled for the fix? I have not seen it in .17 and .18

<!-- gh-comment-id:3096453256 --> @perelin commented on GitHub (Jul 21, 2025): @Ithanil thanks! is there a release scheduled for the fix? I have not seen it in .17 and .18
Author
Owner

@Classic298 commented on GitHub (Jul 21, 2025):

@perelin fix was included in 0.6.16

it's just missing from the changelog is all, but the fix is implemented

and for me, i can confirm it works :D

<!-- gh-comment-id:3096489276 --> @Classic298 commented on GitHub (Jul 21, 2025): @perelin fix was included in 0.6.16 it's just missing from the changelog is all, but the fix is implemented and for me, i can confirm it works :D
Author
Owner

@perelin commented on GitHub (Jul 21, 2025):

Im on 0.6.16 and still get the errors on search. Any way I can support debugging this? A search from just now:

Jul 21 16:07:19 2025-07-21 14:07:19.264 | INFO | uvicorn.protocols.http.httptools_impl:send:476 - 46.223.213.127:0 - "GET /api/v1/chats/all/tags HTTP/1.1" 200 - {}
Jul 21 16:07:21 2025-07-21 14:07:21.967 | INFO | uvicorn.protocols.http.httptools_impl:send:476 - 46.223.213.127:0 - "GET /api/v1/chats/search?text=semax&page=1 HTTP/1.1" 500 - {}
Jul 21 16:07:21 Exception in ASGI application
Jul 21 16:07:21 Traceback (most recent call last):
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1964, in _exec_single_context
Jul 21 16:07:21 self.dialect.do_execute(
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 942, in do_execute
Jul 21 16:07:21 cursor.execute(statement, parameters)
Jul 21 16:07:21 psycopg2.errors.UntranslatableCharacter: unsupported Unicode escape sequence
Jul 21 16:07:21 DETAIL: \u0000 cannot be converted to text.
Jul 21 16:07:21 CONTEXT: JSON data, line 1: ....g., classification models). Some early \u0000...
Jul 21 16:07:21 2025-07-21T14:07:21Z
Jul 21 16:07:21 2025-07-21T14:07:21Z
Jul 21 16:07:21 The above exception was the direct cause of the following exception:
Jul 21 16:07:21 2025-07-21T14:07:21Z
Jul 21 16:07:21 Traceback (most recent call last):
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/uvicorn/protocols/http/httptools_impl.py", line 409, in run_asgi
Jul 21 16:07:21 result = await app( # type: ignore[func-returns-value]
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__
Jul 21 16:07:21 return await self.app(scope, receive, send)
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__
Jul 21 16:07:21 await super().__call__(scope, receive, send)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/applications.py", line 112, in __call__
Jul 21 16:07:21 await self.middleware_stack(scope, receive, send)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 187, in __call__
Jul 21 16:07:21 raise exc
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 165, in __call__
Jul 21 16:07:21 await self.app(scope, receive, _send)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/sessions.py", line 85, in __call__
Jul 21 16:07:21 await self.app(scope, receive, send_wrapper)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/cors.py", line 85, in __call__
Jul 21 16:07:21 await self.app(scope, receive, send)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__
Jul 21 16:07:21 with recv_stream, send_stream, collapse_excgroups():
Jul 21 16:07:21 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__
Jul 21 16:07:21 self.gen.throw(value)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups
Jul 21 16:07:21 raise exc
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__
Jul 21 16:07:21 response = await self.dispatch_func(request, call_next)
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/backend/open_webui/main.py", line 1162, in inspect_websocket
Jul 21 16:07:21 return await call_next(request)
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next
Jul 21 16:07:21 raise app_exc
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro
Jul 21 16:07:21 await self.app(scope, receive_or_disconnect, send_no_error)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__
Jul 21 16:07:21 with recv_stream, send_stream, collapse_excgroups():
Jul 21 16:07:21 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__
Jul 21 16:07:21 self.gen.throw(value)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups
Jul 21 16:07:21 raise exc
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__
Jul 21 16:07:21 response = await self.dispatch_func(request, call_next)
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/backend/open_webui/main.py", line 1141, in check_url
Jul 21 16:07:21 response = await call_next(request)
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next
Jul 21 16:07:21 raise app_exc
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro
Jul 21 16:07:21 await self.app(scope, receive_or_disconnect, send_no_error)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__
Jul 21 16:07:21 with recv_stream, send_stream, collapse_excgroups():
Jul 21 16:07:21 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__
Jul 21 16:07:21 self.gen.throw(value)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups
Jul 21 16:07:21 raise exc
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__
Jul 21 16:07:21 response = await self.dispatch_func(request, call_next)
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/backend/open_webui/main.py", line 1127, in commit_session_after_request
Jul 21 16:07:21 response = await call_next(request)
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next
Jul 21 16:07:21 raise app_exc
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro
Jul 21 16:07:21 await self.app(scope, receive_or_disconnect, send_no_error)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__
Jul 21 16:07:21 with recv_stream, send_stream, collapse_excgroups():
Jul 21 16:07:21 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__
Jul 21 16:07:21 self.gen.throw(value)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups
Jul 21 16:07:21 raise exc
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__
Jul 21 16:07:21 response = await self.dispatch_func(request, call_next)
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/backend/open_webui/utils/security_headers.py", line 11, in dispatch
Jul 21 16:07:21 response = await call_next(request)
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next
Jul 21 16:07:21 raise app_exc
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro
Jul 21 16:07:21 await self.app(scope, receive_or_disconnect, send_no_error)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__
Jul 21 16:07:21 with recv_stream, send_stream, collapse_excgroups():
Jul 21 16:07:21 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__
Jul 21 16:07:21 self.gen.throw(value)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups
Jul 21 16:07:21 raise exc
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__
Jul 21 16:07:21 response = await self.dispatch_func(request, call_next)
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/backend/open_webui/main.py", line 1113, in dispatch
Jul 21 16:07:21 response = await call_next(request)
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next
Jul 21 16:07:21 raise app_exc
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro
Jul 21 16:07:21 await self.app(scope, receive_or_disconnect, send_no_error)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette_compress/__init__.py", line 92, in __call__
Jul 21 16:07:21 return await self._zstd(scope, receive, send)
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette_compress/_zstd_legacy.py", line 100, in __call__
Jul 21 16:07:21 await self.app(scope, receive, wrapper)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 62, in __call__
Jul 21 16:07:21 await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
Jul 21 16:07:21 raise exc
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
Jul 21 16:07:21 await app(scope, receive, sender)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 715, in __call__
Jul 21 16:07:21 await self.middleware_stack(scope, receive, send)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 735, in app
Jul 21 16:07:21 await route.handle(scope, receive, send)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 288, in handle
Jul 21 16:07:21 await self.app(scope, receive, send)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 76, in app
Jul 21 16:07:21 await wrap_app_handling_exceptions(app, request)(scope, receive, send)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
Jul 21 16:07:21 raise exc
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
Jul 21 16:07:21 await app(scope, receive, sender)
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 73, in app
Jul 21 16:07:21 response = await f(request)
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/fastapi/routing.py", line 301, in app
Jul 21 16:07:21 raw_response = await run_endpoint_function(
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/fastapi/routing.py", line 212, in run_endpoint_function
Jul 21 16:07:21 return await dependant.call(**values)
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/backend/open_webui/routers/chats.py", line 172, in search_user_chats
Jul 21 16:07:21 for chat in Chats.get_chats_by_user_id_and_search_text(
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/backend/open_webui/models/chats.py", line 729, in get_chats_by_user_id_and_search_text
Jul 21 16:07:21 all_chats = query.offset(skip).limit(limit).all()
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/query.py", line 2699, in all
Jul 21 16:07:21 return self._iter().all() # type: ignore
Jul 21 16:07:21 ^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/query.py", line 2853, in _iter
Jul 21 16:07:21 result: Union[ScalarResult[_T], Result[_T]] = self.session.execute(
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2365, in execute
Jul 21 16:07:21 return self._execute_internal(
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2251, in _execute_internal
Jul 21 16:07:21 result: Result[Any] = compile_state_cls.orm_execute_statement(
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/context.py", line 305, in orm_execute_statement
Jul 21 16:07:21 result = conn.execute(
Jul 21 16:07:21 ^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
Jul 21 16:07:21 return meth(
Jul 21 16:07:21 ^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection
Jul 21 16:07:21 return connection._execute_clauseelement(
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1638, in _execute_clauseelement
Jul 21 16:07:21 ret = self._execute_context(
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1843, in _execute_context
Jul 21 16:07:21 return self._exec_single_context(
Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1983, in _exec_single_context
Jul 21 16:07:21 self._handle_dbapi_exception(
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 2352, in _handle_dbapi_exception
Jul 21 16:07:21 raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1964, in _exec_single_context
Jul 21 16:07:21 self.dialect.do_execute(
Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 942, in do_execute
Jul 21 16:07:21 cursor.execute(statement, parameters)
Jul 21 16:07:21 sqlalchemy.exc.DataError: (psycopg2.errors.UntranslatableCharacter) unsupported Unicode escape sequence
Jul 21 16:07:21 DETAIL: \u0000 cannot be converted to text.
Jul 21 16:07:21 CONTEXT: JSON data, line 1: ....g., classification models). Some early \u0000...
Jul 21 16:07:21 2025-07-21T14:07:21Z
Jul 21 16:07:21 [SQL: SELECT chat.id AS chat_id, chat.user_id AS chat_user_id, chat.title AS chat_title, chat.chat AS chat_chat, chat.created_at AS chat_created_at, chat.updated_at AS chat_updated_at, chat.share_id AS chat_share_id, chat.archived AS chat_archived, chat.pinned AS chat_pinned, chat.meta AS chat_meta, chat.folder_id AS chat_folder_id
Jul 21 16:07:21 FROM chat
Jul 21 16:07:21 WHERE chat.user_id = %(user_id_1)s AND chat.archived = false AND (chat.title ILIKE %(title_key)s OR EXISTS ( SELECT 1 FROM json_array_elements(Chat.chat->'messages') AS message WHERE LOWER(message->>'content') LIKE '%%' || %(content_key)s || '%%')) ORDER BY chat.updated_at DESC
Jul 21 16:07:21 LIMIT %(param_1)s OFFSET %(param_2)s]
Jul 21 16:07:21 [parameters: {'user_id_1': '2973a326-4694-414a-ac91-52c401d9823b', 'title_key': '%semax%', 'content_key': 'semax', 'param_1': 60, 'param_2': 0}]
Jul 21 16:07:21 (Background on this error at: https://sqlalche.me/e/20/9h9h)
<!-- gh-comment-id:3096942626 --> @perelin commented on GitHub (Jul 21, 2025): Im on 0.6.16 and still get the errors on search. Any way I can support debugging this? A search from just now: ``` Jul 21 16:07:19 2025-07-21 14:07:19.264 | INFO | uvicorn.protocols.http.httptools_impl:send:476 - 46.223.213.127:0 - "GET /api/v1/chats/all/tags HTTP/1.1" 200 - {} Jul 21 16:07:21 2025-07-21 14:07:21.967 | INFO | uvicorn.protocols.http.httptools_impl:send:476 - 46.223.213.127:0 - "GET /api/v1/chats/search?text=semax&page=1 HTTP/1.1" 500 - {} Jul 21 16:07:21 Exception in ASGI application Jul 21 16:07:21 Traceback (most recent call last): Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1964, in _exec_single_context Jul 21 16:07:21 self.dialect.do_execute( Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 942, in do_execute Jul 21 16:07:21 cursor.execute(statement, parameters) Jul 21 16:07:21 psycopg2.errors.UntranslatableCharacter: unsupported Unicode escape sequence Jul 21 16:07:21 DETAIL: \u0000 cannot be converted to text. Jul 21 16:07:21 CONTEXT: JSON data, line 1: ....g., classification models). Some early \u0000... Jul 21 16:07:21 2025-07-21T14:07:21Z Jul 21 16:07:21 2025-07-21T14:07:21Z Jul 21 16:07:21 The above exception was the direct cause of the following exception: Jul 21 16:07:21 2025-07-21T14:07:21Z Jul 21 16:07:21 Traceback (most recent call last): Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/uvicorn/protocols/http/httptools_impl.py", line 409, in run_asgi Jul 21 16:07:21 result = await app( # type: ignore[func-returns-value] Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__ Jul 21 16:07:21 return await self.app(scope, receive, send) Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__ Jul 21 16:07:21 await super().__call__(scope, receive, send) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/applications.py", line 112, in __call__ Jul 21 16:07:21 await self.middleware_stack(scope, receive, send) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 187, in __call__ Jul 21 16:07:21 raise exc Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 165, in __call__ Jul 21 16:07:21 await self.app(scope, receive, _send) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/sessions.py", line 85, in __call__ Jul 21 16:07:21 await self.app(scope, receive, send_wrapper) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/cors.py", line 85, in __call__ Jul 21 16:07:21 await self.app(scope, receive, send) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__ Jul 21 16:07:21 with recv_stream, send_stream, collapse_excgroups(): Jul 21 16:07:21 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__ Jul 21 16:07:21 self.gen.throw(value) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups Jul 21 16:07:21 raise exc Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__ Jul 21 16:07:21 response = await self.dispatch_func(request, call_next) Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/backend/open_webui/main.py", line 1162, in inspect_websocket Jul 21 16:07:21 return await call_next(request) Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next Jul 21 16:07:21 raise app_exc Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro Jul 21 16:07:21 await self.app(scope, receive_or_disconnect, send_no_error) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__ Jul 21 16:07:21 with recv_stream, send_stream, collapse_excgroups(): Jul 21 16:07:21 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__ Jul 21 16:07:21 self.gen.throw(value) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups Jul 21 16:07:21 raise exc Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__ Jul 21 16:07:21 response = await self.dispatch_func(request, call_next) Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/backend/open_webui/main.py", line 1141, in check_url Jul 21 16:07:21 response = await call_next(request) Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next Jul 21 16:07:21 raise app_exc Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro Jul 21 16:07:21 await self.app(scope, receive_or_disconnect, send_no_error) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__ Jul 21 16:07:21 with recv_stream, send_stream, collapse_excgroups(): Jul 21 16:07:21 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__ Jul 21 16:07:21 self.gen.throw(value) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups Jul 21 16:07:21 raise exc Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__ Jul 21 16:07:21 response = await self.dispatch_func(request, call_next) Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/backend/open_webui/main.py", line 1127, in commit_session_after_request Jul 21 16:07:21 response = await call_next(request) Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next Jul 21 16:07:21 raise app_exc Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro Jul 21 16:07:21 await self.app(scope, receive_or_disconnect, send_no_error) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__ Jul 21 16:07:21 with recv_stream, send_stream, collapse_excgroups(): Jul 21 16:07:21 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__ Jul 21 16:07:21 self.gen.throw(value) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups Jul 21 16:07:21 raise exc Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__ Jul 21 16:07:21 response = await self.dispatch_func(request, call_next) Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/backend/open_webui/utils/security_headers.py", line 11, in dispatch Jul 21 16:07:21 response = await call_next(request) Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next Jul 21 16:07:21 raise app_exc Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro Jul 21 16:07:21 await self.app(scope, receive_or_disconnect, send_no_error) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 177, in __call__ Jul 21 16:07:21 with recv_stream, send_stream, collapse_excgroups(): Jul 21 16:07:21 File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__ Jul 21 16:07:21 self.gen.throw(value) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_utils.py", line 82, in collapse_excgroups Jul 21 16:07:21 raise exc Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 179, in __call__ Jul 21 16:07:21 response = await self.dispatch_func(request, call_next) Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/backend/open_webui/main.py", line 1113, in dispatch Jul 21 16:07:21 response = await call_next(request) Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 154, in call_next Jul 21 16:07:21 raise app_exc Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 141, in coro Jul 21 16:07:21 await self.app(scope, receive_or_disconnect, send_no_error) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette_compress/__init__.py", line 92, in __call__ Jul 21 16:07:21 return await self._zstd(scope, receive, send) Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette_compress/_zstd_legacy.py", line 100, in __call__ Jul 21 16:07:21 await self.app(scope, receive, wrapper) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 62, in __call__ Jul 21 16:07:21 await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app Jul 21 16:07:21 raise exc Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app Jul 21 16:07:21 await app(scope, receive, sender) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 715, in __call__ Jul 21 16:07:21 await self.middleware_stack(scope, receive, send) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 735, in app Jul 21 16:07:21 await route.handle(scope, receive, send) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 288, in handle Jul 21 16:07:21 await self.app(scope, receive, send) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 76, in app Jul 21 16:07:21 await wrap_app_handling_exceptions(app, request)(scope, receive, send) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app Jul 21 16:07:21 raise exc Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app Jul 21 16:07:21 await app(scope, receive, sender) Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/starlette/routing.py", line 73, in app Jul 21 16:07:21 response = await f(request) Jul 21 16:07:21 ^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/fastapi/routing.py", line 301, in app Jul 21 16:07:21 raw_response = await run_endpoint_function( Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/fastapi/routing.py", line 212, in run_endpoint_function Jul 21 16:07:21 return await dependant.call(**values) Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/backend/open_webui/routers/chats.py", line 172, in search_user_chats Jul 21 16:07:21 for chat in Chats.get_chats_by_user_id_and_search_text( Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/backend/open_webui/models/chats.py", line 729, in get_chats_by_user_id_and_search_text Jul 21 16:07:21 all_chats = query.offset(skip).limit(limit).all() Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/query.py", line 2699, in all Jul 21 16:07:21 return self._iter().all() # type: ignore Jul 21 16:07:21 ^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/query.py", line 2853, in _iter Jul 21 16:07:21 result: Union[ScalarResult[_T], Result[_T]] = self.session.execute( Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2365, in execute Jul 21 16:07:21 return self._execute_internal( Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2251, in _execute_internal Jul 21 16:07:21 result: Result[Any] = compile_state_cls.orm_execute_statement( Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/orm/context.py", line 305, in orm_execute_statement Jul 21 16:07:21 result = conn.execute( Jul 21 16:07:21 ^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1416, in execute Jul 21 16:07:21 return meth( Jul 21 16:07:21 ^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection Jul 21 16:07:21 return connection._execute_clauseelement( Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1638, in _execute_clauseelement Jul 21 16:07:21 ret = self._execute_context( Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1843, in _execute_context Jul 21 16:07:21 return self._exec_single_context( Jul 21 16:07:21 ^^^^^^^^^^^^^^^^^^^^^^^^^^ Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1983, in _exec_single_context Jul 21 16:07:21 self._handle_dbapi_exception( Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 2352, in _handle_dbapi_exception Jul 21 16:07:21 raise sqlalchemy_exception.with_traceback(exc_info[2]) from e Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1964, in _exec_single_context Jul 21 16:07:21 self.dialect.do_execute( Jul 21 16:07:21 File "/app/code/venv/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 942, in do_execute Jul 21 16:07:21 cursor.execute(statement, parameters) Jul 21 16:07:21 sqlalchemy.exc.DataError: (psycopg2.errors.UntranslatableCharacter) unsupported Unicode escape sequence Jul 21 16:07:21 DETAIL: \u0000 cannot be converted to text. Jul 21 16:07:21 CONTEXT: JSON data, line 1: ....g., classification models). Some early \u0000... Jul 21 16:07:21 2025-07-21T14:07:21Z Jul 21 16:07:21 [SQL: SELECT chat.id AS chat_id, chat.user_id AS chat_user_id, chat.title AS chat_title, chat.chat AS chat_chat, chat.created_at AS chat_created_at, chat.updated_at AS chat_updated_at, chat.share_id AS chat_share_id, chat.archived AS chat_archived, chat.pinned AS chat_pinned, chat.meta AS chat_meta, chat.folder_id AS chat_folder_id Jul 21 16:07:21 FROM chat Jul 21 16:07:21 WHERE chat.user_id = %(user_id_1)s AND chat.archived = false AND (chat.title ILIKE %(title_key)s OR EXISTS ( SELECT 1 FROM json_array_elements(Chat.chat->'messages') AS message WHERE LOWER(message->>'content') LIKE '%%' || %(content_key)s || '%%')) ORDER BY chat.updated_at DESC Jul 21 16:07:21 LIMIT %(param_1)s OFFSET %(param_2)s] Jul 21 16:07:21 [parameters: {'user_id_1': '2973a326-4694-414a-ac91-52c401d9823b', 'title_key': '%semax%', 'content_key': 'semax', 'param_1': 60, 'param_2': 0}] Jul 21 16:07:21 (Background on this error at: https://sqlalche.me/e/20/9h9h) ```
Author
Owner

@rgaricano commented on GitHub (Jul 21, 2025):

I have similar issue because I have an ∀ (\u2200) in my site name, (with postgresql),
a workaround is filter & replace in SQL Clause, but I haven't gotten around to it yet.

<!-- gh-comment-id:3097037326 --> @rgaricano commented on GitHub (Jul 21, 2025): I have similar issue because I have an ∀ (\u2200) in my site name, (with postgresql), a workaround is filter & replace in SQL Clause, but I haven't gotten around to it yet.
Author
Owner

@Classic298 commented on GitHub (Jul 21, 2025):

@perelin I think, based on your traceback, the following might be happening:

My PR #14957 only prevents new null characters from being introduced and from being searched for, but you have existing corrupted JSON data in your PostgreSQL database from before the fix. When PostgreSQL tries to parse these old records during search, it still fails with the same error.

Looking at your log:
CONTEXT: JSON data, line 1: ....g., classification models). Some early \u0000...

This shows PostgreSQL hitting existing corrupted chat records that contain \u0000 characters from before my sanitization was implemented.

To fix this, you need to clean your existing data. Try to let AI build you a very simple script that connects to your database and replaces all null values with nothing in all rows of the chat table

My fix prevents future corruption and incorrect searches but doesn't (and can't!) retroactively clean existing data (would require database manipulation of every single Open WebUI instance). Can you try to build (e.g. with AI) and run a cleanup query and see if that resolves your search functionality?

<!-- gh-comment-id:3097055680 --> @Classic298 commented on GitHub (Jul 21, 2025): @perelin I think, based on your traceback, the following might be happening: My PR #14957 only prevents new null characters **from being introduced** and from **being searched for**, but you have existing corrupted JSON data in your PostgreSQL database from before the fix. When PostgreSQL tries to parse these old records during search, it still fails with the same error. Looking at your log: `CONTEXT: JSON data, line 1: ....g., classification models). Some early \u0000...` This shows PostgreSQL hitting existing corrupted chat records that contain \u0000 characters from before my sanitization was implemented. To fix this, you need to clean your existing data. Try to let AI build you a very simple script that connects to your database and replaces all null values with nothing in all rows of the chat table My fix prevents future corruption and incorrect searches but doesn't (and can't!) retroactively clean existing data (would require database manipulation of every single Open WebUI instance). Can you try to build (e.g. with AI) and run a cleanup query and see if that resolves your search functionality?
Author
Owner

@perelin commented on GitHub (Jul 22, 2025):

@Classic298 alright, thx! I did a manual clean up. Was easy since it was only one entry.

For others with the same problem:

  • find the offending chats in the DB
SELECT   
    id,  
    user_id,  
    title,  
    created_at,  
    updated_at,  
    LENGTH(chat::text) as json_length,  
    POSITION(E'\\u0000' IN chat::text) as null_byte_position  
FROM chat   
WHERE chat::text LIKE '%\u0000%'  
   OR chat::text LIKE '%\\u0000%'  
ORDER BY updated_at DESC;
  • delete chats (one by one) via API with
curl -X DELETE "https://your-openwebui-domain/api/v1/chats/{chat_id}" -H "Authorization: Bearer {your_token}"
<!-- gh-comment-id:3101584310 --> @perelin commented on GitHub (Jul 22, 2025): @Classic298 alright, thx! I did a manual clean up. Was easy since it was only one entry. For others with the same problem: - find the offending chats in the DB ```sql SELECT id, user_id, title, created_at, updated_at, LENGTH(chat::text) as json_length, POSITION(E'\\u0000' IN chat::text) as null_byte_position FROM chat WHERE chat::text LIKE '%\u0000%' OR chat::text LIKE '%\\u0000%' ORDER BY updated_at DESC; ``` - delete chats (one by one) via API with ```sh curl -X DELETE "https://your-openwebui-domain/api/v1/chats/{chat_id}" -H "Authorization: Bearer {your_token}" ```
Author
Owner

@rgaricano commented on GitHub (Jul 22, 2025):

It's ok, but too radical delete the whole chat, maybe just replacing the char should be enought!

<!-- gh-comment-id:3101672418 --> @rgaricano commented on GitHub (Jul 22, 2025): It's ok, but too radical delete the whole chat, maybe just replacing the char should be enought!
Author
Owner

@Classic298 commented on GitHub (Jul 22, 2025):

yes replacing the chars would have been enough

<!-- gh-comment-id:3101764274 --> @Classic298 commented on GitHub (Jul 22, 2025): yes replacing the chars would have been enough
Author
Owner

@mramendi commented on GitHub (Sep 22, 2025):

I am hitting the problem on 0.6.30, and I started this setup on 0.6.28 so no data from older versions could have been there. I don't know which message could even contain null characters, but there are many Cyrillic messages.

Full excerpt from container log here https://pastes.io/openwebui-search-errpr

Key lines:

Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: sqlalchemy.exc.DataError: (psycopg2.errors.UntranslatableCharacter) unsupported Unicode escape sequence
Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: DETAIL:  \u0000 cannot be converted to text.
Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: CONTEXT:  JSON data, line 1: ...07 y[{\u0018\u001av \u00128['o\u000f\u0010 \u0000...
Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: 
Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: [SQL: SELECT chat.id AS chat_id, chat.user_id AS chat_user_id, chat.title AS chat_title, chat.chat AS chat_chat, chat.created_at AS chat_created_at, chat.updated_at AS chat_updated_at, chat.share_id AS chat_share_id, chat.archived AS chat_archived, chat.pinned AS chat_pinned, chat.meta AS chat_meta, chat.folder_id AS chat_folder_id 
Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: FROM chat 
Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: WHERE chat.user_id = %(user_id_1)s AND chat.archived = false AND (chat.title ILIKE %(title_key)s OR EXISTS (    SELECT 1     FROM json_array_elements(Chat.chat->'messages') AS message     WHERE LOWER(message->>'content') LIKE '%%' || %(content_key)s || '%%')) ORDER BY chat.updated_at DESC 
Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]:  LIMIT %(param_1)s OFFSET %(param_2)s]
Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: [parameters: {'user_id_1': 'cb57db65-3eed-4a99-b253-0cd3a22b2056', 'title_key': '%test%', 'content_key': 'test', 'param_1': 60, 'param_2': 0}]
Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: (Background on this error at: https://sqlalche.me/e/20/9h9h)
<!-- gh-comment-id:3316678128 --> @mramendi commented on GitHub (Sep 22, 2025): I am hitting the problem on 0.6.30, and I started this setup on 0.6.28 so no data from older versions could have been there. I don't know which message could even contain null characters, but there are many Cyrillic messages. Full excerpt from container log here https://pastes.io/openwebui-search-errpr Key lines: ``` Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: sqlalchemy.exc.DataError: (psycopg2.errors.UntranslatableCharacter) unsupported Unicode escape sequence Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: DETAIL: \u0000 cannot be converted to text. Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: CONTEXT: JSON data, line 1: ...07 y[{\u0018\u001av \u00128['o\u000f\u0010 \u0000... Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: [SQL: SELECT chat.id AS chat_id, chat.user_id AS chat_user_id, chat.title AS chat_title, chat.chat AS chat_chat, chat.created_at AS chat_created_at, chat.updated_at AS chat_updated_at, chat.share_id AS chat_share_id, chat.archived AS chat_archived, chat.pinned AS chat_pinned, chat.meta AS chat_meta, chat.folder_id AS chat_folder_id Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: FROM chat Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: WHERE chat.user_id = %(user_id_1)s AND chat.archived = false AND (chat.title ILIKE %(title_key)s OR EXISTS ( SELECT 1 FROM json_array_elements(Chat.chat->'messages') AS message WHERE LOWER(message->>'content') LIKE '%%' || %(content_key)s || '%%')) ORDER BY chat.updated_at DESC Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: LIMIT %(param_1)s OFFSET %(param_2)s] Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: [parameters: {'user_id_1': 'cb57db65-3eed-4a99-b253-0cd3a22b2056', 'title_key': '%test%', 'content_key': 'test', 'param_1': 60, 'param_2': 0}] Sep 21 04:32:09 de2.ramendik.eu openwebui-playwright-openwebui[210722]: (Background on this error at: https://sqlalche.me/e/20/9h9h)
Author
Owner

@mramendi commented on GitHub (Sep 22, 2025):

Forensics and then an attempt at recovery, all guided by the inimitable Kimi K2, which remembered a similar but from 0.3.x first - but it was embeddings then, full chat texts now.

openwebui=> SELECT id, title, created_at
FROM chat
WHERE chat::text ~ '\\u0000'
LIMIT 5;
                  id                  |                                                     title                                                     | created_at 
--------------------------------------+---------------------------------------------------------------------------------------------------------------+------------
 42712a57-51a0-479a-8695-8c41c36cb67c | Cascading LLMs for Routing 🤖                                                                                  | 1758039259
 fcba5a70-76e3-4a6b-8e81-be3baf9e6504 | Types of Tests 🧪                                                                                              | 1758059079
 184b2d0f-d02f-44aa-870b-b745fb91786b | 💸 GLM-4-32B Tuning Pricing                                                                                    | 1758116576
 42f8d374-dd71-46b3-a960-4743f5330974 | I want you to find providers that offer GLM-4-32b (specifically that model and not just GLM-4.5) on the cloud | 1758143541
 c4ebdc7d-f373-4f13-a31f-4afdcfc53c98 | GLM-4-32B Cloud Providers ☁                                                                                   | 1758144244
(5 rows)

openwebui=> SELECT id, title, created_at
FROM chat
WHERE chat::text ~ '\\u0000';
                  id                  |                                                     title                                                     | created_at 
--------------------------------------+---------------------------------------------------------------------------------------------------------------+------------
 42712a57-51a0-479a-8695-8c41c36cb67c | Cascading LLMs for Routing 🤖                                                                                  | 1758039259
 fcba5a70-76e3-4a6b-8e81-be3baf9e6504 | Types of Tests 🧪                                                                                              | 1758059079
 184b2d0f-d02f-44aa-870b-b745fb91786b | 💸 GLM-4-32B Tuning Pricing                                                                                    | 1758116576
 42f8d374-dd71-46b3-a960-4743f5330974 | I want you to find providers that offer GLM-4-32b (specifically that model and not just GLM-4.5) on the cloud | 1758143541
 c4ebdc7d-f373-4f13-a31f-4afdcfc53c98 | GLM-4-32B Cloud Providers ☁                                                                                   | 1758144244
 0702ec22-3318-460a-ae73-ece07057f49c | Two-Stage LLM Memory Filtering 🤖                                                                              | 1758151637
 026d94a7-3e23-4779-b981-dd3bb36b6dd5 | Xi Jinping Biography 📚                                                                                        | 1758214649
 4967b2a9-bd6e-4e40-bf80-ab4fab14e8ea | Types of Tests 🧪                                                                                              | 1758244205
 a4b72d00-cee8-4435-85c9-1240a1f7dfd8 | DIY Sassy AI Clone 🤖                                                                                          | 1758492491
(9 rows)

openwebui=>  
                  id                  |                                                                                                   left                                              
                                                     | created_at 
--------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------+------------
 42712a57-51a0-479a-8695-8c41c36cb67c | {"id": "", "title": "Cascading LLMs for Routing \ud83e\udd16", "models": ["kimi-k2-vercel-fireworks"], "params": {}, "history": {"messages": {"59eac
140-6350-432d-bbe1-10347137fe79": {"id": "59eac140-6 | 1758039259
 fcba5a70-76e3-4a6b-8e81-be3baf9e6504 | {"id": "", "title": "Types of Tests \ud83e\uddea", "models": ["gemini-2.5-pro"], "params": {}, "history": {"messages": {"eb34ff01-0274-40aa-b953-fc7
7f1a138d2": {"id": "eb34ff01-0274-40aa-b953-fc77f1a1 | 1758059079
 184b2d0f-d02f-44aa-870b-b745fb91786b | {"id": "", "title": "\ud83d\udcb8 GLM-4-32B Tuning Pricing", "models": ["gemini-2.5-pro"], "params": {}, "history": {"messages": {"bb7854b7-673e-47c
7-89d3-41c28dfa915c": {"id": "bb7854b7-673e-47c7-89d | 1758116576
 42f8d374-dd71-46b3-a960-4743f5330974 | {"id": "", "title": "I want you to find providers that offer GLM-4-32b (specifically that model and not just GLM-4.5) on the cloud", "models": ["Qwe
n3 235B A22B Thinking 2507"], "params": {}, "history | 1758143541
 c4ebdc7d-f373-4f13-a31f-4afdcfc53c98 | {"id": "", "title": "GLM-4-32B Cloud Providers \u2601\ufe0f", "models": ["gpt-5-nano"], "params": {}, "history": {"messages": {"c348f0e6-da21-45f5-b
580-0194e178fcde": {"id": "c348f0e6-da21-45f5-b580-0 | 1758144244
 0702ec22-3318-460a-ae73-ece07057f49c | {"id": "", "title": "Two-Stage LLM Memory Filtering \ud83e\udd16", "models": ["Qwen3 235B A22B Instruct 2507"], "params": {}, "history": {"messages"
: {"0308ede9-de95-4c83-85ab-cd357a6d778d": {"id": "0 | 1758151637
 026d94a7-3e23-4779-b981-dd3bb36b6dd5 | {"id": "", "title": "Xi Jinping Biography 📚", "models": ["Qwen3 235B A22B Instruct 2507"], "params": {}, "history": {"messages": {"3333820a-42c3-41
03-981d-9c4400c7b95c": {"id": "3333820a-42c3-4103-981 | 1758214649
 4967b2a9-bd6e-4e40-bf80-ab4fab14e8ea | {"id": "", "title": "Types of Tests 🧪", "models": ["Qwen3 235B A22B Instruct 2507"], "params": {}, "history": {"messages": {"c9366252-fb20-408c-907
f-c38f7c43d444": {"id": "c9366252-fb20-408c-907f-c38f | 1758244205
 a4b72d00-cee8-4435-85c9-1240a1f7dfd8 | {"id": "", "title": "DIY Sassy AI Clone \ud83e\udd16", "models": ["kimi-k2"], "params": {}, "history": {"messages": {"098e2969-7597-41d1-b278-f0290e
390808": {"id": "098e2969-7597-41d1-b278-f0290e39080 | 1758492491
(9 rows)

This was the forensics and while some of the threads did bug out the others are perfectly normal, the last one is still ongoing and that's where Kimi is coming up with these commands. So I try its suggested patch attempts:

openwebui=> UPDATE chat
SET chat = regexp_replace(chat::text, '\\u0000', '', 'g')::jsonb
WHERE chat::text ~ '\\u0000';
ERROR:  invalid input syntax for type json
DETAIL:  Escape sequence "\_" is invalid.
CONTEXT:  JSON data, line 1: ...\\\\u0010Y\\\\u0014\\\\u0011&amp;\\\\\\\u000e\\\_...
openwebui=> UPDATE chat
SET chat = regexp_replace(chat::text,
                          '[\\u0000-\\u001f\\u007f-\u009f\\\\&]', '', 'g')::jsonb
WHERE chat::text ~ '\\u0000';
ERROR:  invalid input syntax for type json
DETAIL:  Expected end of input, but found """".
CONTEXT:  JSON data, line 1: "d" ""...

At this point Kimi thinks the JSON itself is broken and suggests a Python script, and we had quite a few turns getting it to do something, as attempts to just strip null bytes seemed to not work. What finally worked is this overkill that also removed valid discussion of \u0000 :

import json,os,psycopg2, re

def strip_ctrl(obj):
    if isinstance(obj, dict):
        return {k: strip_ctrl(v) for k, v in obj.items()}
    if isinstance(obj, list):
        return [strip_ctrl(elem) for elem in obj]
    if isinstance(obj, str):
        # remove NUL and the rest of C0 controls
        return re.sub(r'\\u000[0-9a-fA-F]', '', obj)
    return obj

conn=psycopg2.connect(os.getenv('DATABASE_URL'))
cur=conn.cursor()
cur.execute("SELECT id, chat FROM chat WHERE chat::text LIKE '%\\u0000%';")
rows=cur.fetchall()
for rid,doc in rows:
    try:
        cleaned = strip_ctrl(doc)          # walk & scrub
        dumped = json.dumps(cleaned, ensure_ascii=False)
        if '\\u0000' in dumped:
            print('still dirty ->', rid)
            continue
        cur.execute("UPDATE chat SET chat = %s WHERE id = %s", (dumped, rid))

    except Exception as e:
        print('skipping bad row',rid,e)
conn.commit()

at that point things got fixed and Kimi suggested a trigger to prevent future additions, but the first version of the trigger stopped all additions. Kimi suggested that something was calling exceptions within the trigger so it rewrite it to never raise, but now I don't know if it even does anything.

openwebui=> CREATE OR REPLACE FUNCTION safe_strip_nul()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
   -- try to remove NUL byte; on any error return the original JSON unchanged
   BEGIN
      NEW.chat := replace(NEW.chat::bytea, '\000'::bytea, ''::bytea)::jsonb;
   EXCEPTION
      WHEN OTHERS THEN  -- catches malformed JSON, etc.
         NULL;          -- leave NEW.chat untouched
   END;
   RETURN NEW;
END;
$$;

CREATE TRIGGER chat_safe_nul
BEFORE INSERT OR UPDATE ON chat
FOR EACH ROW
EXECUTE FUNCTION safe_strip_nul();

For now I have a working system thanks to a sassy and cavalier model (cavalier indeed, it never once suggested a backup of the database and its script ideas involved running the regex on serialized JSON - the recursive idea is actually from Qwen3 235B A22 Instruct but its own script was over 100 lines and I think it had other bugs). But I do hope this can be fixed. What happened here is most of the listed threads bugged out with the model aborting or me pressing Stop - that probably generates a null character and/or some other controls. The string needs to be sanitized for those before being written to PostgreSQL, but hopefully in a way that does not kill mere mentioning of \u0000 as a sequence.

<!-- gh-comment-id:3317013786 --> @mramendi commented on GitHub (Sep 22, 2025): Forensics and then an attempt at recovery, all guided by the inimitable Kimi K2, which remembered a similar but from 0.3.x first - but it was embeddings then, full chat texts now. ``` openwebui=> SELECT id, title, created_at FROM chat WHERE chat::text ~ '\\u0000' LIMIT 5; id | title | created_at --------------------------------------+---------------------------------------------------------------------------------------------------------------+------------ 42712a57-51a0-479a-8695-8c41c36cb67c | Cascading LLMs for Routing 🤖 | 1758039259 fcba5a70-76e3-4a6b-8e81-be3baf9e6504 | Types of Tests 🧪 | 1758059079 184b2d0f-d02f-44aa-870b-b745fb91786b | 💸 GLM-4-32B Tuning Pricing | 1758116576 42f8d374-dd71-46b3-a960-4743f5330974 | I want you to find providers that offer GLM-4-32b (specifically that model and not just GLM-4.5) on the cloud | 1758143541 c4ebdc7d-f373-4f13-a31f-4afdcfc53c98 | GLM-4-32B Cloud Providers ☁ | 1758144244 (5 rows) openwebui=> SELECT id, title, created_at FROM chat WHERE chat::text ~ '\\u0000'; id | title | created_at --------------------------------------+---------------------------------------------------------------------------------------------------------------+------------ 42712a57-51a0-479a-8695-8c41c36cb67c | Cascading LLMs for Routing 🤖 | 1758039259 fcba5a70-76e3-4a6b-8e81-be3baf9e6504 | Types of Tests 🧪 | 1758059079 184b2d0f-d02f-44aa-870b-b745fb91786b | 💸 GLM-4-32B Tuning Pricing | 1758116576 42f8d374-dd71-46b3-a960-4743f5330974 | I want you to find providers that offer GLM-4-32b (specifically that model and not just GLM-4.5) on the cloud | 1758143541 c4ebdc7d-f373-4f13-a31f-4afdcfc53c98 | GLM-4-32B Cloud Providers ☁ | 1758144244 0702ec22-3318-460a-ae73-ece07057f49c | Two-Stage LLM Memory Filtering 🤖 | 1758151637 026d94a7-3e23-4779-b981-dd3bb36b6dd5 | Xi Jinping Biography 📚 | 1758214649 4967b2a9-bd6e-4e40-bf80-ab4fab14e8ea | Types of Tests 🧪 | 1758244205 a4b72d00-cee8-4435-85c9-1240a1f7dfd8 | DIY Sassy AI Clone 🤖 | 1758492491 (9 rows) openwebui=> id | left | created_at --------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------- -----------------------------------------------------+------------ 42712a57-51a0-479a-8695-8c41c36cb67c | {"id": "", "title": "Cascading LLMs for Routing \ud83e\udd16", "models": ["kimi-k2-vercel-fireworks"], "params": {}, "history": {"messages": {"59eac 140-6350-432d-bbe1-10347137fe79": {"id": "59eac140-6 | 1758039259 fcba5a70-76e3-4a6b-8e81-be3baf9e6504 | {"id": "", "title": "Types of Tests \ud83e\uddea", "models": ["gemini-2.5-pro"], "params": {}, "history": {"messages": {"eb34ff01-0274-40aa-b953-fc7 7f1a138d2": {"id": "eb34ff01-0274-40aa-b953-fc77f1a1 | 1758059079 184b2d0f-d02f-44aa-870b-b745fb91786b | {"id": "", "title": "\ud83d\udcb8 GLM-4-32B Tuning Pricing", "models": ["gemini-2.5-pro"], "params": {}, "history": {"messages": {"bb7854b7-673e-47c 7-89d3-41c28dfa915c": {"id": "bb7854b7-673e-47c7-89d | 1758116576 42f8d374-dd71-46b3-a960-4743f5330974 | {"id": "", "title": "I want you to find providers that offer GLM-4-32b (specifically that model and not just GLM-4.5) on the cloud", "models": ["Qwe n3 235B A22B Thinking 2507"], "params": {}, "history | 1758143541 c4ebdc7d-f373-4f13-a31f-4afdcfc53c98 | {"id": "", "title": "GLM-4-32B Cloud Providers \u2601\ufe0f", "models": ["gpt-5-nano"], "params": {}, "history": {"messages": {"c348f0e6-da21-45f5-b 580-0194e178fcde": {"id": "c348f0e6-da21-45f5-b580-0 | 1758144244 0702ec22-3318-460a-ae73-ece07057f49c | {"id": "", "title": "Two-Stage LLM Memory Filtering \ud83e\udd16", "models": ["Qwen3 235B A22B Instruct 2507"], "params": {}, "history": {"messages" : {"0308ede9-de95-4c83-85ab-cd357a6d778d": {"id": "0 | 1758151637 026d94a7-3e23-4779-b981-dd3bb36b6dd5 | {"id": "", "title": "Xi Jinping Biography 📚", "models": ["Qwen3 235B A22B Instruct 2507"], "params": {}, "history": {"messages": {"3333820a-42c3-41 03-981d-9c4400c7b95c": {"id": "3333820a-42c3-4103-981 | 1758214649 4967b2a9-bd6e-4e40-bf80-ab4fab14e8ea | {"id": "", "title": "Types of Tests 🧪", "models": ["Qwen3 235B A22B Instruct 2507"], "params": {}, "history": {"messages": {"c9366252-fb20-408c-907 f-c38f7c43d444": {"id": "c9366252-fb20-408c-907f-c38f | 1758244205 a4b72d00-cee8-4435-85c9-1240a1f7dfd8 | {"id": "", "title": "DIY Sassy AI Clone \ud83e\udd16", "models": ["kimi-k2"], "params": {}, "history": {"messages": {"098e2969-7597-41d1-b278-f0290e 390808": {"id": "098e2969-7597-41d1-b278-f0290e39080 | 1758492491 (9 rows) ``` This was the forensics and while some of the threads did bug out the others are perfectly normal, the last one is still ongoing and that's where Kimi is coming up with these commands. So I try its suggested patch attempts: ``` openwebui=> UPDATE chat SET chat = regexp_replace(chat::text, '\\u0000', '', 'g')::jsonb WHERE chat::text ~ '\\u0000'; ERROR: invalid input syntax for type json DETAIL: Escape sequence "\_" is invalid. CONTEXT: JSON data, line 1: ...\\\\u0010Y\\\\u0014\\\\u0011&amp;\\\\\\\u000e\\\_... openwebui=> UPDATE chat SET chat = regexp_replace(chat::text, '[\\u0000-\\u001f\\u007f-\u009f\\\\&]', '', 'g')::jsonb WHERE chat::text ~ '\\u0000'; ERROR: invalid input syntax for type json DETAIL: Expected end of input, but found """". CONTEXT: JSON data, line 1: "d" ""... ``` At this point Kimi thinks the JSON itself is broken and suggests a Python script, and we had quite a few turns getting it to do something, as attempts to just strip null bytes seemed to not work. What finally worked is this overkill that also removed valid discussion of `\u0000` : ``` import json,os,psycopg2, re def strip_ctrl(obj): if isinstance(obj, dict): return {k: strip_ctrl(v) for k, v in obj.items()} if isinstance(obj, list): return [strip_ctrl(elem) for elem in obj] if isinstance(obj, str): # remove NUL and the rest of C0 controls return re.sub(r'\\u000[0-9a-fA-F]', '', obj) return obj conn=psycopg2.connect(os.getenv('DATABASE_URL')) cur=conn.cursor() cur.execute("SELECT id, chat FROM chat WHERE chat::text LIKE '%\\u0000%';") rows=cur.fetchall() for rid,doc in rows: try: cleaned = strip_ctrl(doc) # walk & scrub dumped = json.dumps(cleaned, ensure_ascii=False) if '\\u0000' in dumped: print('still dirty ->', rid) continue cur.execute("UPDATE chat SET chat = %s WHERE id = %s", (dumped, rid)) except Exception as e: print('skipping bad row',rid,e) conn.commit() ``` at that point things got fixed and Kimi suggested a trigger to prevent future additions, but the first version of the trigger stopped all additions. Kimi suggested that something was calling exceptions within the trigger so it rewrite it to never raise, but now I don't know if it even does anything. ``` openwebui=> CREATE OR REPLACE FUNCTION safe_strip_nul() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN -- try to remove NUL byte; on any error return the original JSON unchanged BEGIN NEW.chat := replace(NEW.chat::bytea, '\000'::bytea, ''::bytea)::jsonb; EXCEPTION WHEN OTHERS THEN -- catches malformed JSON, etc. NULL; -- leave NEW.chat untouched END; RETURN NEW; END; $$; CREATE TRIGGER chat_safe_nul BEFORE INSERT OR UPDATE ON chat FOR EACH ROW EXECUTE FUNCTION safe_strip_nul(); ``` For now I have a working system thanks to a sassy and cavalier model (cavalier indeed, it never once suggested a backup of the database and its script ideas involved running the regex on serialized JSON - the recursive idea is actually from Qwen3 235B A22 Instruct but its own script was over 100 lines and I think it had other bugs). But I do hope this can be fixed. What happened here is most of the listed threads bugged out with the model aborting or me pressing Stop - that probably generates a null character and/or some other controls. The string needs to be sanitized for those before being written to PostgreSQL, but hopefully in a way that does not kill mere mentioning of `\u0000` as a sequence.
Author
Owner

@Ithanil commented on GitHub (Sep 22, 2025):

Forensics and then an attempt at recovery, all guided by the inimitable Kimi K2, which remembered a similar but from 0.3.x first - but it was embeddings then, full chat texts now.

openwebui=> SELECT id, title, created_at
FROM chat
WHERE chat::text ~ '\\u0000'
LIMIT 5;
                  id                  |                                                     title                                                     | created_at 
--------------------------------------+---------------------------------------------------------------------------------------------------------------+------------
 42712a57-51a0-479a-8695-8c41c36cb67c | Cascading LLMs for Routing 🤖                                                                                  | 1758039259
 fcba5a70-76e3-4a6b-8e81-be3baf9e6504 | Types of Tests 🧪                                                                                              | 1758059079
 184b2d0f-d02f-44aa-870b-b745fb91786b | 💸 GLM-4-32B Tuning Pricing                                                                                    | 1758116576
 42f8d374-dd71-46b3-a960-4743f5330974 | I want you to find providers that offer GLM-4-32b (specifically that model and not just GLM-4.5) on the cloud | 1758143541
 c4ebdc7d-f373-4f13-a31f-4afdcfc53c98 | GLM-4-32B Cloud Providers ☁                                                                                   | 1758144244
(5 rows)

openwebui=> SELECT id, title, created_at
FROM chat
WHERE chat::text ~ '\\u0000';
                  id                  |                                                     title                                                     | created_at 
--------------------------------------+---------------------------------------------------------------------------------------------------------------+------------
 42712a57-51a0-479a-8695-8c41c36cb67c | Cascading LLMs for Routing 🤖                                                                                  | 1758039259
 fcba5a70-76e3-4a6b-8e81-be3baf9e6504 | Types of Tests 🧪                                                                                              | 1758059079
 184b2d0f-d02f-44aa-870b-b745fb91786b | 💸 GLM-4-32B Tuning Pricing                                                                                    | 1758116576
 42f8d374-dd71-46b3-a960-4743f5330974 | I want you to find providers that offer GLM-4-32b (specifically that model and not just GLM-4.5) on the cloud | 1758143541
 c4ebdc7d-f373-4f13-a31f-4afdcfc53c98 | GLM-4-32B Cloud Providers ☁                                                                                   | 1758144244
 0702ec22-3318-460a-ae73-ece07057f49c | Two-Stage LLM Memory Filtering 🤖                                                                              | 1758151637
 026d94a7-3e23-4779-b981-dd3bb36b6dd5 | Xi Jinping Biography 📚                                                                                        | 1758214649
 4967b2a9-bd6e-4e40-bf80-ab4fab14e8ea | Types of Tests 🧪                                                                                              | 1758244205
 a4b72d00-cee8-4435-85c9-1240a1f7dfd8 | DIY Sassy AI Clone 🤖                                                                                          | 1758492491
(9 rows)

openwebui=>  
                  id                  |                                                                                                   left                                              
                                                     | created_at 
--------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------+------------
 42712a57-51a0-479a-8695-8c41c36cb67c | {"id": "", "title": "Cascading LLMs for Routing \ud83e\udd16", "models": ["kimi-k2-vercel-fireworks"], "params": {}, "history": {"messages": {"59eac
140-6350-432d-bbe1-10347137fe79": {"id": "59eac140-6 | 1758039259
 fcba5a70-76e3-4a6b-8e81-be3baf9e6504 | {"id": "", "title": "Types of Tests \ud83e\uddea", "models": ["gemini-2.5-pro"], "params": {}, "history": {"messages": {"eb34ff01-0274-40aa-b953-fc7
7f1a138d2": {"id": "eb34ff01-0274-40aa-b953-fc77f1a1 | 1758059079
 184b2d0f-d02f-44aa-870b-b745fb91786b | {"id": "", "title": "\ud83d\udcb8 GLM-4-32B Tuning Pricing", "models": ["gemini-2.5-pro"], "params": {}, "history": {"messages": {"bb7854b7-673e-47c
7-89d3-41c28dfa915c": {"id": "bb7854b7-673e-47c7-89d | 1758116576
 42f8d374-dd71-46b3-a960-4743f5330974 | {"id": "", "title": "I want you to find providers that offer GLM-4-32b (specifically that model and not just GLM-4.5) on the cloud", "models": ["Qwe
n3 235B A22B Thinking 2507"], "params": {}, "history | 1758143541
 c4ebdc7d-f373-4f13-a31f-4afdcfc53c98 | {"id": "", "title": "GLM-4-32B Cloud Providers \u2601\ufe0f", "models": ["gpt-5-nano"], "params": {}, "history": {"messages": {"c348f0e6-da21-45f5-b
580-0194e178fcde": {"id": "c348f0e6-da21-45f5-b580-0 | 1758144244
 0702ec22-3318-460a-ae73-ece07057f49c | {"id": "", "title": "Two-Stage LLM Memory Filtering \ud83e\udd16", "models": ["Qwen3 235B A22B Instruct 2507"], "params": {}, "history": {"messages"
: {"0308ede9-de95-4c83-85ab-cd357a6d778d": {"id": "0 | 1758151637
 026d94a7-3e23-4779-b981-dd3bb36b6dd5 | {"id": "", "title": "Xi Jinping Biography 📚", "models": ["Qwen3 235B A22B Instruct 2507"], "params": {}, "history": {"messages": {"3333820a-42c3-41
03-981d-9c4400c7b95c": {"id": "3333820a-42c3-4103-981 | 1758214649
 4967b2a9-bd6e-4e40-bf80-ab4fab14e8ea | {"id": "", "title": "Types of Tests 🧪", "models": ["Qwen3 235B A22B Instruct 2507"], "params": {}, "history": {"messages": {"c9366252-fb20-408c-907
f-c38f7c43d444": {"id": "c9366252-fb20-408c-907f-c38f | 1758244205
 a4b72d00-cee8-4435-85c9-1240a1f7dfd8 | {"id": "", "title": "DIY Sassy AI Clone \ud83e\udd16", "models": ["kimi-k2"], "params": {}, "history": {"messages": {"098e2969-7597-41d1-b278-f0290e
390808": {"id": "098e2969-7597-41d1-b278-f0290e39080 | 1758492491
(9 rows)

This was the forensics and while some of the threads did bug out the others are perfectly normal, the last one is still ongoing and that's where Kimi is coming up with these commands. So I try its suggested patch attempts:

openwebui=> UPDATE chat
SET chat = regexp_replace(chat::text, '\\u0000', '', 'g')::jsonb
WHERE chat::text ~ '\\u0000';
ERROR:  invalid input syntax for type json
DETAIL:  Escape sequence "\_" is invalid.
CONTEXT:  JSON data, line 1: ...\\\\u0010Y\\\\u0014\\\\u0011&amp;\\\\\\\u000e\\\_...
openwebui=> UPDATE chat
SET chat = regexp_replace(chat::text,
                          '[\\u0000-\\u001f\\u007f-\u009f\\\\&]', '', 'g')::jsonb
WHERE chat::text ~ '\\u0000';
ERROR:  invalid input syntax for type json
DETAIL:  Expected end of input, but found """".
CONTEXT:  JSON data, line 1: "d" ""...

At this point Kimi thinks the JSON itself is broken and suggests a Python script, and we had quite a few turns getting it to do something, as attempts to just strip null bytes seemed to not work. What finally worked is this overkill that also removed valid discussion of \u0000 :

import json,os,psycopg2, re

def strip_ctrl(obj):
    if isinstance(obj, dict):
        return {k: strip_ctrl(v) for k, v in obj.items()}
    if isinstance(obj, list):
        return [strip_ctrl(elem) for elem in obj]
    if isinstance(obj, str):
        # remove NUL and the rest of C0 controls
        return re.sub(r'\\u000[0-9a-fA-F]', '', obj)
    return obj

conn=psycopg2.connect(os.getenv('DATABASE_URL'))
cur=conn.cursor()
cur.execute("SELECT id, chat FROM chat WHERE chat::text LIKE '%\\u0000%';")
rows=cur.fetchall()
for rid,doc in rows:
    try:
        cleaned = strip_ctrl(doc)          # walk & scrub
        dumped = json.dumps(cleaned, ensure_ascii=False)
        if '\\u0000' in dumped:
            print('still dirty ->', rid)
            continue
        cur.execute("UPDATE chat SET chat = %s WHERE id = %s", (dumped, rid))

    except Exception as e:
        print('skipping bad row',rid,e)
conn.commit()

at that point things got fixed and Kimi suggested a trigger to prevent future additions, but the first version of the trigger stopped all additions. Kimi suggested that something was calling exceptions within the trigger so it rewrite it to never raise, but now I don't know if it even does anything.

openwebui=> CREATE OR REPLACE FUNCTION safe_strip_nul()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
   -- try to remove NUL byte; on any error return the original JSON unchanged
   BEGIN
      NEW.chat := replace(NEW.chat::bytea, '\000'::bytea, ''::bytea)::jsonb;
   EXCEPTION
      WHEN OTHERS THEN  -- catches malformed JSON, etc.
         NULL;          -- leave NEW.chat untouched
   END;
   RETURN NEW;
END;
$$;

CREATE TRIGGER chat_safe_nul
BEFORE INSERT OR UPDATE ON chat
FOR EACH ROW
EXECUTE FUNCTION safe_strip_nul();

For now I have a working system thanks to a sassy and cavalier model (cavalier indeed, it never once suggested a backup of the database and its script ideas involved running the regex on serialized JSON - the recursive idea is actually from Qwen3 235B A22 Instruct but its own script was over 100 lines and I think it had other bugs). But I do hope this can be fixed. What happened here is most of the listed threads bugged out with the model aborting or me pressing Stop - that probably generates a null character and/or some other controls. The string needs to be sanitized for those before being written to PostgreSQL, but hopefully in a way that does not kill mere mentioning of \u0000 as a sequence.

That's all overly complicated, check the sanitize option of my cleanup script: https://github.com/Ithanil/openwebui-scripts/blob/update/scripts/cleanup_pg/cleanup_pg.py (but note that the whole script is meant to delete old chats and orphaned data).

Nevertheless, unfortunately it indeed still happens that \u0000 character make it into chats somehow @Classic298

<!-- gh-comment-id:3317029542 --> @Ithanil commented on GitHub (Sep 22, 2025): > Forensics and then an attempt at recovery, all guided by the inimitable Kimi K2, which remembered a similar but from 0.3.x first - but it was embeddings then, full chat texts now. > > ``` > openwebui=> SELECT id, title, created_at > FROM chat > WHERE chat::text ~ '\\u0000' > LIMIT 5; > id | title | created_at > --------------------------------------+---------------------------------------------------------------------------------------------------------------+------------ > 42712a57-51a0-479a-8695-8c41c36cb67c | Cascading LLMs for Routing 🤖 | 1758039259 > fcba5a70-76e3-4a6b-8e81-be3baf9e6504 | Types of Tests 🧪 | 1758059079 > 184b2d0f-d02f-44aa-870b-b745fb91786b | 💸 GLM-4-32B Tuning Pricing | 1758116576 > 42f8d374-dd71-46b3-a960-4743f5330974 | I want you to find providers that offer GLM-4-32b (specifically that model and not just GLM-4.5) on the cloud | 1758143541 > c4ebdc7d-f373-4f13-a31f-4afdcfc53c98 | GLM-4-32B Cloud Providers ☁ | 1758144244 > (5 rows) > > openwebui=> SELECT id, title, created_at > FROM chat > WHERE chat::text ~ '\\u0000'; > id | title | created_at > --------------------------------------+---------------------------------------------------------------------------------------------------------------+------------ > 42712a57-51a0-479a-8695-8c41c36cb67c | Cascading LLMs for Routing 🤖 | 1758039259 > fcba5a70-76e3-4a6b-8e81-be3baf9e6504 | Types of Tests 🧪 | 1758059079 > 184b2d0f-d02f-44aa-870b-b745fb91786b | 💸 GLM-4-32B Tuning Pricing | 1758116576 > 42f8d374-dd71-46b3-a960-4743f5330974 | I want you to find providers that offer GLM-4-32b (specifically that model and not just GLM-4.5) on the cloud | 1758143541 > c4ebdc7d-f373-4f13-a31f-4afdcfc53c98 | GLM-4-32B Cloud Providers ☁ | 1758144244 > 0702ec22-3318-460a-ae73-ece07057f49c | Two-Stage LLM Memory Filtering 🤖 | 1758151637 > 026d94a7-3e23-4779-b981-dd3bb36b6dd5 | Xi Jinping Biography 📚 | 1758214649 > 4967b2a9-bd6e-4e40-bf80-ab4fab14e8ea | Types of Tests 🧪 | 1758244205 > a4b72d00-cee8-4435-85c9-1240a1f7dfd8 | DIY Sassy AI Clone 🤖 | 1758492491 > (9 rows) > > openwebui=> > id | left > | created_at > --------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------- > -----------------------------------------------------+------------ > 42712a57-51a0-479a-8695-8c41c36cb67c | {"id": "", "title": "Cascading LLMs for Routing \ud83e\udd16", "models": ["kimi-k2-vercel-fireworks"], "params": {}, "history": {"messages": {"59eac > 140-6350-432d-bbe1-10347137fe79": {"id": "59eac140-6 | 1758039259 > fcba5a70-76e3-4a6b-8e81-be3baf9e6504 | {"id": "", "title": "Types of Tests \ud83e\uddea", "models": ["gemini-2.5-pro"], "params": {}, "history": {"messages": {"eb34ff01-0274-40aa-b953-fc7 > 7f1a138d2": {"id": "eb34ff01-0274-40aa-b953-fc77f1a1 | 1758059079 > 184b2d0f-d02f-44aa-870b-b745fb91786b | {"id": "", "title": "\ud83d\udcb8 GLM-4-32B Tuning Pricing", "models": ["gemini-2.5-pro"], "params": {}, "history": {"messages": {"bb7854b7-673e-47c > 7-89d3-41c28dfa915c": {"id": "bb7854b7-673e-47c7-89d | 1758116576 > 42f8d374-dd71-46b3-a960-4743f5330974 | {"id": "", "title": "I want you to find providers that offer GLM-4-32b (specifically that model and not just GLM-4.5) on the cloud", "models": ["Qwe > n3 235B A22B Thinking 2507"], "params": {}, "history | 1758143541 > c4ebdc7d-f373-4f13-a31f-4afdcfc53c98 | {"id": "", "title": "GLM-4-32B Cloud Providers \u2601\ufe0f", "models": ["gpt-5-nano"], "params": {}, "history": {"messages": {"c348f0e6-da21-45f5-b > 580-0194e178fcde": {"id": "c348f0e6-da21-45f5-b580-0 | 1758144244 > 0702ec22-3318-460a-ae73-ece07057f49c | {"id": "", "title": "Two-Stage LLM Memory Filtering \ud83e\udd16", "models": ["Qwen3 235B A22B Instruct 2507"], "params": {}, "history": {"messages" > : {"0308ede9-de95-4c83-85ab-cd357a6d778d": {"id": "0 | 1758151637 > 026d94a7-3e23-4779-b981-dd3bb36b6dd5 | {"id": "", "title": "Xi Jinping Biography 📚", "models": ["Qwen3 235B A22B Instruct 2507"], "params": {}, "history": {"messages": {"3333820a-42c3-41 > 03-981d-9c4400c7b95c": {"id": "3333820a-42c3-4103-981 | 1758214649 > 4967b2a9-bd6e-4e40-bf80-ab4fab14e8ea | {"id": "", "title": "Types of Tests 🧪", "models": ["Qwen3 235B A22B Instruct 2507"], "params": {}, "history": {"messages": {"c9366252-fb20-408c-907 > f-c38f7c43d444": {"id": "c9366252-fb20-408c-907f-c38f | 1758244205 > a4b72d00-cee8-4435-85c9-1240a1f7dfd8 | {"id": "", "title": "DIY Sassy AI Clone \ud83e\udd16", "models": ["kimi-k2"], "params": {}, "history": {"messages": {"098e2969-7597-41d1-b278-f0290e > 390808": {"id": "098e2969-7597-41d1-b278-f0290e39080 | 1758492491 > (9 rows) > ``` > > This was the forensics and while some of the threads did bug out the others are perfectly normal, the last one is still ongoing and that's where Kimi is coming up with these commands. So I try its suggested patch attempts: > > ``` > openwebui=> UPDATE chat > SET chat = regexp_replace(chat::text, '\\u0000', '', 'g')::jsonb > WHERE chat::text ~ '\\u0000'; > ERROR: invalid input syntax for type json > DETAIL: Escape sequence "\_" is invalid. > CONTEXT: JSON data, line 1: ...\\\\u0010Y\\\\u0014\\\\u0011&amp;\\\\\\\u000e\\\_... > openwebui=> UPDATE chat > SET chat = regexp_replace(chat::text, > '[\\u0000-\\u001f\\u007f-\u009f\\\\&]', '', 'g')::jsonb > WHERE chat::text ~ '\\u0000'; > ERROR: invalid input syntax for type json > DETAIL: Expected end of input, but found """". > CONTEXT: JSON data, line 1: "d" ""... > ``` > > At this point Kimi thinks the JSON itself is broken and suggests a Python script, and we had quite a few turns getting it to do something, as attempts to just strip null bytes seemed to not work. What finally worked is this overkill that also removed valid discussion of `\u0000` : > > ``` > import json,os,psycopg2, re > > def strip_ctrl(obj): > if isinstance(obj, dict): > return {k: strip_ctrl(v) for k, v in obj.items()} > if isinstance(obj, list): > return [strip_ctrl(elem) for elem in obj] > if isinstance(obj, str): > # remove NUL and the rest of C0 controls > return re.sub(r'\\u000[0-9a-fA-F]', '', obj) > return obj > > conn=psycopg2.connect(os.getenv('DATABASE_URL')) > cur=conn.cursor() > cur.execute("SELECT id, chat FROM chat WHERE chat::text LIKE '%\\u0000%';") > rows=cur.fetchall() > for rid,doc in rows: > try: > cleaned = strip_ctrl(doc) # walk & scrub > dumped = json.dumps(cleaned, ensure_ascii=False) > if '\\u0000' in dumped: > print('still dirty ->', rid) > continue > cur.execute("UPDATE chat SET chat = %s WHERE id = %s", (dumped, rid)) > > except Exception as e: > print('skipping bad row',rid,e) > conn.commit() > ``` > > at that point things got fixed and Kimi suggested a trigger to prevent future additions, but the first version of the trigger stopped all additions. Kimi suggested that something was calling exceptions within the trigger so it rewrite it to never raise, but now I don't know if it even does anything. > > ``` > openwebui=> CREATE OR REPLACE FUNCTION safe_strip_nul() > RETURNS TRIGGER LANGUAGE plpgsql AS $$ > BEGIN > -- try to remove NUL byte; on any error return the original JSON unchanged > BEGIN > NEW.chat := replace(NEW.chat::bytea, '\000'::bytea, ''::bytea)::jsonb; > EXCEPTION > WHEN OTHERS THEN -- catches malformed JSON, etc. > NULL; -- leave NEW.chat untouched > END; > RETURN NEW; > END; > $$; > > CREATE TRIGGER chat_safe_nul > BEFORE INSERT OR UPDATE ON chat > FOR EACH ROW > EXECUTE FUNCTION safe_strip_nul(); > ``` > > For now I have a working system thanks to a sassy and cavalier model (cavalier indeed, it never once suggested a backup of the database and its script ideas involved running the regex on serialized JSON - the recursive idea is actually from Qwen3 235B A22 Instruct but its own script was over 100 lines and I think it had other bugs). But I do hope this can be fixed. What happened here is most of the listed threads bugged out with the model aborting or me pressing Stop - that probably generates a null character and/or some other controls. The string needs to be sanitized for those before being written to PostgreSQL, but hopefully in a way that does not kill mere mentioning of `\u0000` as a sequence. That's all overly complicated, check the sanitize option of my cleanup script: https://github.com/Ithanil/openwebui-scripts/blob/update/scripts/cleanup_pg/cleanup_pg.py (but note that the whole script is meant to **delete** old chats and orphaned data). Nevertheless, unfortunately it indeed still happens that \u0000 character make it into chats somehow @Classic298
Author
Owner

@Classic298 commented on GitHub (Sep 22, 2025):

Thanks, will reopen the issue and when I have time I'll look into it, maybe there are other points of entry for data I did not think about

<!-- gh-comment-id:3317041127 --> @Classic298 commented on GitHub (Sep 22, 2025): Thanks, will reopen the issue and when I have time I'll look into it, maybe there are other points of entry for data I did not think about
Author
Owner

@mramendi commented on GitHub (Sep 23, 2025):

I removed the trigger because I was getting a hang that I am... still getting, so maybe the trigger was not the issue (hang is unrelated - as in, streaming from a model seems to stop, I switch to another chat and back to ths one and see the completed result; I'll try to collect enough info for its own bug).

So my search stopped again and I went looking for the offending row. And as far as I could work out from the dump, all the messages where this is happening have the role "assistant" but don't contain an assistant response, instead they contain a tool response. The tool in question is a web page scraper (I think it's the built-in one). So, the tool response contains null characters and these get written to the database. EDIT: I now realize what happened is the tool pulled raw PDF files; while I'll fix my search pathway to either avoid or decode PDF, the pgsql thing still needs fixing.

I hope this helps pinpoint/fix the issue.

<!-- gh-comment-id:3323871220 --> @mramendi commented on GitHub (Sep 23, 2025): I removed the trigger because I was getting a hang that I am... still getting, so maybe the trigger was not the issue (hang is unrelated - as in, streaming from a model seems to stop, I switch to another chat and back to ths one and see the completed result; I'll try to collect enough info for its own bug). So my search stopped again and I went looking for the offending row. And as far as I could work out from the dump, all the messages where this is happening have the role "assistant" but don't contain an assistant response, instead they contain a tool response. The tool in question is a web page scraper (I think it's the built-in one). So, the tool response contains null characters and these get written to the database. EDIT: I now realize what happened is the tool pulled raw PDF files; while I'll fix my search pathway to either avoid or decode PDF, the pgsql thing still needs fixing. I hope this helps pinpoint/fix the issue.
Author
Owner

@Classic298 commented on GitHub (Sep 23, 2025):

interesting. Tool use and document ingestion.

<!-- gh-comment-id:3323909923 --> @Classic298 commented on GitHub (Sep 23, 2025): interesting. Tool use and document ingestion.
Author
Owner

@rgaricano commented on GitHub (Sep 23, 2025):

and this solution for prevent this kind of issues and gain in security?:
(as prevention: always declare the correct client encoding (SET client_encoding TO 'UTF8'; or use the right psql -E/driver settings) and store binary data in bytea, not text/json )

Answer

You're asking for the specific code implementation to set PostgreSQL client encoding to UTF-8 and properly handle binary data storage in Open WebUI's database connections .

Main Database Connection Encoding

For the main database connection in backend/open_webui/internal/db.py, you need to modify the PostgreSQL engine creation to include client encoding 1 :

else:
    if isinstance(DATABASE_POOL_SIZE, int):
        if DATABASE_POOL_SIZE > 0:
            engine = create_engine(
                SQLALCHEMY_DATABASE_URL,
                pool_size=DATABASE_POOL_SIZE,
                max_overflow=DATABASE_POOL_MAX_OVERFLOW,
                pool_timeout=DATABASE_POOL_TIMEOUT,
                pool_recycle=DATABASE_POOL_RECYCLE,
                pool_pre_ping=True,
                poolclass=QueuePool,
                connect_args={"client_encoding": "utf8"}  # Add this line
            )
        else:
            engine = create_engine(
                SQLALCHEMY_DATABASE_URL, 
                pool_pre_ping=True, 
                poolclass=NullPool,
                connect_args={"client_encoding": "utf8"}  # Add this line
            )
    else:
        engine = create_engine(
            SQLALCHEMY_DATABASE_URL, 
            pool_pre_ping=True,
            connect_args={"client_encoding": "utf8"}  # Add this line
        )

Environment Variable Configuration

You can also set the encoding through the DATABASE_URL environment variable 2 :

# In your environment or configuration
DATABASE_URL = "postgresql://user:pass@host:port/db?client_encoding=utf8"

Connection Event Handler for Encoding

Add an event listener to ensure encoding is set on every connection:

# Add this after the engine creation in backend/open_webui/internal/db.py
def set_postgresql_encoding(dbapi_connection, connection_record):
    if 'postgresql' in SQLALCHEMY_DATABASE_URL:
        cursor = dbapi_connection.cursor()
        cursor.execute("SET client_encoding TO 'UTF8'")
        cursor.close()

event.listen(engine, "connect", set_postgresql_encoding)

Binary Data Storage Implementation

For binary data that shouldn't be stored as JSON/text, you can create a new column type. Here's an example for storing encrypted chat data:

# In backend/open_webui/models/chats.py - add new column for binary data
from sqlalchemy import LargeBinary

class Chat(Base):
    __tablename__ = "chat"
    
    # ... existing columns ...
    chat = Column(JSON)  # Keep existing for compatibility
    chat_binary = Column(LargeBinary, nullable=True)  # New binary storage

Migration for Binary Storage

Create a migration to add binary storage capability:

# New migration file
def upgrade():
    # Add binary column for sensitive data
    op.add_column('chat', sa.Column('chat_binary', sa.LargeBinary(), nullable=True))
    
    # Add function to encrypt and store binary data
    op.execute("""
        CREATE OR REPLACE FUNCTION store_chat_binary(chat_id text, data bytea)
        RETURNS void AS $$
        BEGIN
            UPDATE chat SET chat_binary = data WHERE id = chat_id;
        END;
        $$ LANGUAGE plpgsql;
    """)

Utility Functions for Binary Handling

Add utility functions to handle binary data conversion:

# In backend/open_webui/models/chats.py
import base64
from cryptography.fernet import Fernet

def encrypt_chat_data(data: dict, key: bytes) -> bytes:
    """Encrypt chat data for binary storage"""
    f = Fernet(key)
    json_str = json.dumps(data)
    return f.encrypt(json_str.encode('utf-8'))

def decrypt_chat_data(encrypted_data: bytes, key: bytes) -> dict:
    """Decrypt binary chat data"""
    f = Fernet(key)
    decrypted = f.decrypt(encrypted_data)
    return json.loads(decrypted.decode('utf-8'))

def store_chat_as_binary(self, chat_id: str, chat_data: dict):
    """Store chat data as encrypted binary"""
    if ENABLE_CHAT_ENCRYPTION:
        encrypted_data = encrypt_chat_data(chat_data, CHAT_ENCRYPTION_KEY)
        with get_db() as db:
            chat_item = db.get(Chat, chat_id)
            chat_item.chat_binary = encrypted_data
            db.commit()

Notes

This implementation provides multiple layers of protection: client encoding ensures proper UTF-8 handling at the connection level, while binary storage with bytea provides a secure way to store sensitive data that might contain problematic characters. The existing JSON storage 3 can be maintained for compatibility while adding binary storage for enhanced security.

Citations

File: backend/open_webui/internal/db.py (L128-145)

else:
    if isinstance(DATABASE_POOL_SIZE, int):
        if DATABASE_POOL_SIZE > 0:
            engine = create_engine(
                SQLALCHEMY_DATABASE_URL,
                pool_size=DATABASE_POOL_SIZE,
                max_overflow=DATABASE_POOL_MAX_OVERFLOW,
                pool_timeout=DATABASE_POOL_TIMEOUT,
                pool_recycle=DATABASE_POOL_RECYCLE,
                pool_pre_ping=True,
                poolclass=QueuePool,
            )
        else:
            engine = create_engine(
                SQLALCHEMY_DATABASE_URL, pool_pre_ping=True, poolclass=NullPool
            )
    else:
        engine = create_engine(SQLALCHEMY_DATABASE_URL, pool_pre_ping=True)

File: backend/open_webui/env.py (L272-279)

DATABASE_URL = os.environ.get("DATABASE_URL", f"sqlite:///{DATA_DIR}/webui.db")

DATABASE_TYPE = os.environ.get("DATABASE_TYPE")
DATABASE_USER = os.environ.get("DATABASE_USER")
DATABASE_PASSWORD = os.environ.get("DATABASE_PASSWORD")

DATABASE_CRED = ""
if DATABASE_USER:

File: backend/open_webui/models/chats.py (L32-32)

    chat = Column(JSON)
<!-- gh-comment-id:3324388885 --> @rgaricano commented on GitHub (Sep 23, 2025): and this solution for prevent this kind of issues and gain in security?: (as prevention: always declare the correct client encoding (SET client_encoding TO 'UTF8'; or use the right psql -E/driver settings) and store binary data in bytea, not text/json ) ## Answer You're asking for the specific code implementation to set PostgreSQL client encoding to UTF-8 and properly handle binary data storage in Open WebUI's database connections <cite/>. ### Main Database Connection Encoding For the main database connection in `backend/open_webui/internal/db.py`, you need to modify the PostgreSQL engine creation to include client encoding [1](#4-0) : ```python else: if isinstance(DATABASE_POOL_SIZE, int): if DATABASE_POOL_SIZE > 0: engine = create_engine( SQLALCHEMY_DATABASE_URL, pool_size=DATABASE_POOL_SIZE, max_overflow=DATABASE_POOL_MAX_OVERFLOW, pool_timeout=DATABASE_POOL_TIMEOUT, pool_recycle=DATABASE_POOL_RECYCLE, pool_pre_ping=True, poolclass=QueuePool, connect_args={"client_encoding": "utf8"} # Add this line ) else: engine = create_engine( SQLALCHEMY_DATABASE_URL, pool_pre_ping=True, poolclass=NullPool, connect_args={"client_encoding": "utf8"} # Add this line ) else: engine = create_engine( SQLALCHEMY_DATABASE_URL, pool_pre_ping=True, connect_args={"client_encoding": "utf8"} # Add this line ) ``` ### Environment Variable Configuration You can also set the encoding through the `DATABASE_URL` environment variable [2](#4-1) : ```python # In your environment or configuration DATABASE_URL = "postgresql://user:pass@host:port/db?client_encoding=utf8" ``` ### Connection Event Handler for Encoding Add an event listener to ensure encoding is set on every connection: ```python # Add this after the engine creation in backend/open_webui/internal/db.py def set_postgresql_encoding(dbapi_connection, connection_record): if 'postgresql' in SQLALCHEMY_DATABASE_URL: cursor = dbapi_connection.cursor() cursor.execute("SET client_encoding TO 'UTF8'") cursor.close() event.listen(engine, "connect", set_postgresql_encoding) ``` ### Binary Data Storage Implementation For binary data that shouldn't be stored as JSON/text, you can create a new column type. Here's an example for storing encrypted chat data: ```python # In backend/open_webui/models/chats.py - add new column for binary data from sqlalchemy import LargeBinary class Chat(Base): __tablename__ = "chat" # ... existing columns ... chat = Column(JSON) # Keep existing for compatibility chat_binary = Column(LargeBinary, nullable=True) # New binary storage ``` ### Migration for Binary Storage Create a migration to add binary storage capability: ```python # New migration file def upgrade(): # Add binary column for sensitive data op.add_column('chat', sa.Column('chat_binary', sa.LargeBinary(), nullable=True)) # Add function to encrypt and store binary data op.execute(""" CREATE OR REPLACE FUNCTION store_chat_binary(chat_id text, data bytea) RETURNS void AS $$ BEGIN UPDATE chat SET chat_binary = data WHERE id = chat_id; END; $$ LANGUAGE plpgsql; """) ``` ### Utility Functions for Binary Handling Add utility functions to handle binary data conversion: ```python # In backend/open_webui/models/chats.py import base64 from cryptography.fernet import Fernet def encrypt_chat_data(data: dict, key: bytes) -> bytes: """Encrypt chat data for binary storage""" f = Fernet(key) json_str = json.dumps(data) return f.encrypt(json_str.encode('utf-8')) def decrypt_chat_data(encrypted_data: bytes, key: bytes) -> dict: """Decrypt binary chat data""" f = Fernet(key) decrypted = f.decrypt(encrypted_data) return json.loads(decrypted.decode('utf-8')) def store_chat_as_binary(self, chat_id: str, chat_data: dict): """Store chat data as encrypted binary""" if ENABLE_CHAT_ENCRYPTION: encrypted_data = encrypt_chat_data(chat_data, CHAT_ENCRYPTION_KEY) with get_db() as db: chat_item = db.get(Chat, chat_id) chat_item.chat_binary = encrypted_data db.commit() ``` ## Notes This implementation provides multiple layers of protection: client encoding ensures proper UTF-8 handling at the connection level, while binary storage with `bytea` provides a secure way to store sensitive data that might contain problematic characters. The existing JSON storage [3](#4-2) can be maintained for compatibility while adding binary storage for enhanced security. ### Citations **File:** backend/open_webui/internal/db.py (L128-145) ```python else: if isinstance(DATABASE_POOL_SIZE, int): if DATABASE_POOL_SIZE > 0: engine = create_engine( SQLALCHEMY_DATABASE_URL, pool_size=DATABASE_POOL_SIZE, max_overflow=DATABASE_POOL_MAX_OVERFLOW, pool_timeout=DATABASE_POOL_TIMEOUT, pool_recycle=DATABASE_POOL_RECYCLE, pool_pre_ping=True, poolclass=QueuePool, ) else: engine = create_engine( SQLALCHEMY_DATABASE_URL, pool_pre_ping=True, poolclass=NullPool ) else: engine = create_engine(SQLALCHEMY_DATABASE_URL, pool_pre_ping=True) ``` **File:** backend/open_webui/env.py (L272-279) ```python DATABASE_URL = os.environ.get("DATABASE_URL", f"sqlite:///{DATA_DIR}/webui.db") DATABASE_TYPE = os.environ.get("DATABASE_TYPE") DATABASE_USER = os.environ.get("DATABASE_USER") DATABASE_PASSWORD = os.environ.get("DATABASE_PASSWORD") DATABASE_CRED = "" if DATABASE_USER: ``` **File:** backend/open_webui/models/chats.py (L32-32) ```python chat = Column(JSON) ```
Author
Owner

@Classic298 commented on GitHub (Sep 23, 2025):

You can also set the encoding through the DATABASE_URL environment variable 2

should we add this to the docs?

<!-- gh-comment-id:3324395417 --> @Classic298 commented on GitHub (Sep 23, 2025): > You can also set the encoding through the DATABASE_URL environment variable [2](https://github.com/open-webui/open-webui/issues/15616#4-1) should we add this to the docs?
Author
Owner

@rgaricano commented on GitHub (Sep 23, 2025):

I think that without encoding the situation is the same, e.g. db can be setup for utf8 but I'm using latin-1 in my chats and introducing "UntranslatableCharacter" errors.

<!-- gh-comment-id:3324412457 --> @rgaricano commented on GitHub (Sep 23, 2025): I think that without encoding the situation is the same, e.g. db can be setup for utf8 but I'm using latin-1 in my chats and introducing "UntranslatableCharacter" errors.
Author
Owner

@Classic298 commented on GitHub (Sep 24, 2025):

https://github.com/open-webui/open-webui/pull/17723

<!-- gh-comment-id:3330573100 --> @Classic298 commented on GitHub (Sep 24, 2025): https://github.com/open-webui/open-webui/pull/17723
Author
Owner

@Classic298 commented on GitHub (Sep 24, 2025):

should be a more central and all-round solution

of course doesnt work for already existing data, needs to be manually cleaned once via script

<!-- gh-comment-id:3330579983 --> @Classic298 commented on GitHub (Sep 24, 2025): should be a more central and all-round solution of course doesnt work for already existing data, needs to be manually cleaned once via script
Author
Owner

@rgaricano commented on GitHub (Sep 24, 2025):

I don't think that sanitazion the json is the best option, yes, it can solve the problem and maybe usefull for title and other IA generated text, but it can change the sense of the information of other data provided.
The de/encryption json strings & binary storage solution seem most robust for preserving data.

<!-- gh-comment-id:3330717978 --> @rgaricano commented on GitHub (Sep 24, 2025): I don't think that sanitazion the json is the best option, yes, it can solve the problem and maybe usefull for title and other IA generated text, but it can change the sense of the information of other data provided. The de/encryption json strings & binary storage solution seem most robust for preserving data.
Author
Owner

@Classic298 commented on GitHub (Sep 25, 2025):

hm how it could change the sense of the information?

I am only substituting null strings

<!-- gh-comment-id:3332293620 --> @Classic298 commented on GitHub (Sep 25, 2025): hm how it could change the sense of the information? I am only substituting null strings
Author
Owner

@Classic298 commented on GitHub (Sep 26, 2025):

https://github.com/open-webui/open-webui/pull/17775

<!-- gh-comment-id:3337637876 --> @Classic298 commented on GitHub (Sep 26, 2025): https://github.com/open-webui/open-webui/pull/17775
Author
Owner

@mramendi commented on GitHub (Sep 27, 2025):

I suspect that the currently proposed approach might be sanitizing things a bit too late. The null bytes appear, in my experience, either because of nonstandard tool responses (PDF file stuck in without even bothering with base64) or broken LLM responses (inference server weirds out and responds with a null). These should be caught before they are inducted into the context.

<!-- gh-comment-id:3341946812 --> @mramendi commented on GitHub (Sep 27, 2025): I suspect that the currently proposed approach might be sanitizing things a bit too late. The null bytes appear, in my experience, either because of nonstandard tool responses (PDF file stuck in without even bothering with base64) or broken LLM responses (inference server weirds out and responds with a null). These should be caught before they are inducted into the context.
Author
Owner

@Classic298 commented on GitHub (Sep 28, 2025):

@mramendi while you're right in principle that data should be sanitized as soon as possible, it is easier to implement at the level that is proposed in #17775 and it is sufficient to fix the postgreSQL errors, because the errors stem from the search; and the data does get sanitized before inserted into the database, ensuring these errors should not appear anymore and the search to work again.

It will not make a difference in the end, as long as the data is sanitized before sending it to the database, and I am not sure additional complexity to ensure data is sanitized earlier to this solution will be beneficial

<!-- gh-comment-id:3342809173 --> @Classic298 commented on GitHub (Sep 28, 2025): @mramendi while you're right in principle that data should be sanitized as soon as possible, it is easier to implement at the level that is proposed in #17775 and it is sufficient to fix the postgreSQL errors, because the errors stem from the search; and the data does get sanitized before inserted into the database, ensuring these errors should not appear anymore and the search to work again. It will not make a difference in the end, as long as the data is sanitized before sending it to the database, and I am not sure additional complexity to ensure data is sanitized earlier to this solution will be beneficial
Author
Owner

@abiari commented on GitHub (Oct 8, 2025):

@Classic298 I went through all the issues about this subject, did my own tests and found out that sanitizing "\x00" doesn't take care of '\u0000' (which gave me the worst headache trying to query search my pg db) as well, they might be identical in value, but when running a query to search the chat json, '\u0000' doesn't seem to be identical to '\x00'.

I managed to clean my db by replacing '\u0000' with an escaped '\\u0000', queries run with no errors now, and context is preserved in my chats

Edit: "\\u0000" didn't help for all cases, some being binary file data from web scraping. Still need to find and replace '\u0000' with an empty string

<!-- gh-comment-id:3380980196 --> @abiari commented on GitHub (Oct 8, 2025): @Classic298 I went through all the issues about this subject, did my own tests and found out that sanitizing "\x00" doesn't take care of '\u0000' (which gave me the worst headache trying to query search my pg db) as well, they might be identical in value, but when running a query to search the chat json, '\u0000' doesn't seem to be identical to '\x00'. I managed to clean my db by replacing '\u0000' with an escaped '\\\\u0000', queries run with no errors now, and context is preserved in my chats Edit: "\\\\u0000" didn't help for all cases, some being binary file data from web scraping. Still need to find and replace '\u0000' with an empty string
Author
Owner

@Classic298 commented on GitHub (Oct 9, 2025):

@abiari I have made a slight modification to my proposed PR

Can you test it again with a web search or a file you know for sure causes problems?

The thing is: \x00 and \u0000 are the same exact thing just in different notations.
I hope my new code will now correctly interpret and remove all these null characters. Testing wanted!

<!-- gh-comment-id:3384877009 --> @Classic298 commented on GitHub (Oct 9, 2025): @abiari I have made a slight modification to my proposed PR Can you test it again with a web search or a file you know for sure causes problems? The thing is: \x00 and \u0000 are the **same exact thing** just in different notations. I hope my new code will now correctly interpret and remove all these null characters. Testing wanted!
Author
Owner

@abiari commented on GitHub (Oct 9, 2025):

@Classic298 I tested your approach, the search an replace is actually done on the data as strings, the replace function will look for the '\x00' string and won't match on '\u0000'

<!-- gh-comment-id:3385607152 --> @abiari commented on GitHub (Oct 9, 2025): @Classic298 I tested your approach, the search an replace is actually done on the data as strings, the replace function will look for the '\x00' string and won't match on '\u0000'
Author
Owner

@abiari commented on GitHub (Oct 9, 2025):

I don't think that sanitazion the json is the best option, yes, it can solve the problem and maybe usefull for title and other IA generated text, but it can change the sense of the information of other data provided. The de/encryption json strings & binary storage solution seem most robust for preserving data.

I very much agree with this as well, a de/encryption of documents/images data should be preferable to preserve context.

<!-- gh-comment-id:3385611765 --> @abiari commented on GitHub (Oct 9, 2025): > I don't think that sanitazion the json is the best option, yes, it can solve the problem and maybe usefull for title and other IA generated text, but it can change the sense of the information of other data provided. The de/encryption json strings & binary storage solution seem most robust for preserving data. I very much agree with this as well, a de/encryption of documents/images data should be preferable to preserve context.
Author
Owner

@Classic298 commented on GitHub (Oct 9, 2025):

But how so? When is null data ever useful?

Null bytes are NEVER legitimate in text data

They only exist in Binary data (PDFs, images - which are not being searched with the search function anyways and cannot be sanitized either, so that doesn't matter here)

Binary data / null data shouldn't be in text fields, or in responses (that would make them corrupted responses)

The encryption suggestion is overkill imo. This isn't about security, it's about data format

I tested your approach, the search an replace is actually done on the data as strings, the replace function will look for the '\x00' string and won't match on '\u0000'

Can you show it to me please? What did you test or how did you test it?

the .replace function does not search the exact string matches in the strings as you say! It WILL match \x00 with \u0000!

import json

# Create a string with actual null byte
test = "hello\u0000world"
print(repr(test))  # will be shown as 'hello\x00world'

# Try replace
cleaned = test.replace('\x00', '') # \x00 will replace \u0000 as well
print(repr(cleaned))  # clean 'helloworld' output as proof

# Show JSON encoding issue
json_str = json.dumps(test)
print(json_str)  # '"hello\\u0000world"' ← PostgreSQL rejects this

# Show sanitized version
json_clean = json.dumps(cleaned)
print(json_clean)  # '"helloworld"' ← PostgreSQL accepts this
<!-- gh-comment-id:3386025602 --> @Classic298 commented on GitHub (Oct 9, 2025): But how so? When is null data ever useful? Null bytes are NEVER legitimate in text data They only exist in Binary data (PDFs, images - which are not being searched with the search function anyways and cannot be sanitized either, so that doesn't matter here) Binary data / null data shouldn't be in text fields, or in responses (that would make them corrupted responses) The encryption suggestion is overkill imo. This isn't about security, it's about data format > I tested your approach, the search an replace is actually done on the data as strings, the replace function will look for the '\x00' string and won't match on '\u0000' Can you show it to me please? What did you test or how did you test it? the .replace function does not search the exact string matches in the strings as you say! **It WILL match \x00 with \u0000!** ```python import json # Create a string with actual null byte test = "hello\u0000world" print(repr(test)) # will be shown as 'hello\x00world' # Try replace cleaned = test.replace('\x00', '') # \x00 will replace \u0000 as well print(repr(cleaned)) # clean 'helloworld' output as proof # Show JSON encoding issue json_str = json.dumps(test) print(json_str) # '"hello\\u0000world"' ← PostgreSQL rejects this # Show sanitized version json_clean = json.dumps(cleaned) print(json_clean) # '"helloworld"' ← PostgreSQL accepts this ```
Author
Owner

@Classic298 commented on GitHub (Oct 9, 2025):

Run this code for yourself. You will see that \x00 WILL replace the \u0000 string @abiari

<!-- gh-comment-id:3386029566 --> @Classic298 commented on GitHub (Oct 9, 2025): Run this code for yourself. You will see that \x00 WILL replace the \u0000 string @abiari
Author
Owner

@Classic298 commented on GitHub (Nov 23, 2025):

fixed in dev

<!-- gh-comment-id:3567717898 --> @Classic298 commented on GitHub (Nov 23, 2025): fixed 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#56284