Changelog for 0.11.5: Redis per-room delivery, revoked channel and note access, Docling file names, the 0.11.4 interface language regression and the other fixes since v0.11.4, plus a Changed entry asking Redis-backed fleets to update all instances together.
With native function calling on an Ollama model, every request sent after a tool result was missing the model's system prompt, so the final answer ignored the model's instructions. Only other system content, such as the attached knowledge tag, was left. OpenAI connections were not affected.
Tool-call follow-ups are rebuilt from the chat's message list and skip the router's system prompt step, because the first request is expected to have already added it to that list. The OpenAI path does add it there, but the Ollama path converts the messages into a copy first and adds the prompt only to the copy, so the follow-ups never see it.
The model system prompt is now applied to the messages before the Ollama conversion, and the Ollama router is told to skip it for that request so it is not added twice. Ollama now behaves the same as the OpenAI path. Direct calls to /ollama/api/chat still get the prompt from the router as before.
Checked baseline against patched: first request and follow-up for plain, custom and arena Ollama models, with and without a chat system prompt, with template variables and on the OpenAI path. The prompt is now present exactly once on every Ollama follow-up, and nothing else changed.
Fixes#30161
Clicking a folder name in the sidebar while a chat was open switched that chat to the folder's default model for a moment before the page changed. That reset its tools and skills to the folder model's set and saved them as the chat's draft, so on returning to the chat the folder model's tools were shown and sent with the next message. The same happened to a chat just started from the home page.
The folder's default model is now only applied while the chat has no messages yet, the same rule new chats already follow when a folder page opens. Folder pages, new chats in a folder and editing a folder's default model behave as before.
Verified in a browser against a mock upstream: on dev the next request after the folder click carried the folder model's tool_ids and skill_ids; with the fix it carries the chat's own model's set, for both an existing chat and one started from the home page.
Fixes#30226
* fix: keep en-US as the last i18n fallback when a stored locale has no bundle
Settings labels rendered as raw keys such as settings.admin.connections.title
instead of "Connections" from the second page load on, while every other
label looked fine.
On the first visit the language detector saves whatever the browser reports
into localStorage, including bare codes like "en" that have no locale bundle.
The same value is saved by ?lang=en or DEFAULT_LOCALE=en. On the next load
that stored value became the only fallback language, so the missing bundle
left nothing to fall back to. Plain keys still looked right because the key
is the English text; only the settings.* keys have a distinct value.
en-US now stays at the end of the fallback list whenever a stored locale is
passed in. The stored locale keeps precedence, real locales are unaffected,
and the en-US bundle was already loaded for every language for the settings
merge, so there is no extra request.
Fixes#30348
* fix: match browser language codes to a locale bundle
Firefox reports German, Dutch and Polish as bare codes (de, nl, pl), both Chrome and Firefox report Japanese as ja, and Chrome in Latin America reports es-419. No bundle is keyed by any of these. The page layout used to match them on the first visit, but since 67ac1a4e9 (0.11.4) awaits the i18n init, the detector has already cached the raw code by the time that check runs, so it never does. Those users get an English interface, a language dropdown with nothing selected and English date formats, and the cached code keeps them there.
The detector now maps a reported code onto a bundle before anything else sees it. A code we ship stays as it is, matched case-insensitively. Otherwise the language's xx-XX bundle is used when there is one, so de and de-AT become de-DE, es-419 becomes es-ES, and fr becomes fr-FR even though fr-CA comes first in the list. A bare code with no xx-XX bundle takes the first one for its language, so ja becomes ja-JP. Any other regional code passes through untouched, so zh-HK is not sent to Simplified zh-CN, and no locale that works today changes.
Restoring the layout check would leave every browser that cached a raw code under 0.11.4 stuck. Matching in the detector migrates them on their next load, because the matched code is what gets cached. {{USER_LANGUAGE}} reports that matched code too, so es-419 users now send es-ES.
Verified against the real bundles on first and repeat loads, including the untouched cases fr-CA, pt-BR, zh-TW, zh-HK, ar-BH, en-GB and uz-Latn-UZ.
Reworked from the ground up after the feedback that the registry implementation did not land. The Redis room registry and its whole recovery protocol (heartbeats, liveness keys, pruning, distrust windows, cache invalidation) are gone; the change is now ~105 lines with no state kept outside the process.
With WEBSOCKET_MANAGER=redis every emit is published on one shared channel and every instance JSON-decodes every message: a 16 instance fleet decodes each streamed token delta 16 times and 15 discard it. py-spy across a loaded fleet (16 instances, ~4000 users) puts ~31% of all active CPU samples in the pubsub listener parse chain, the largest bucket.
Room-targeted emits are now published on a per-room channel instead; every instance keeps one static pattern subscription covering all room channels and drops messages for rooms without local members by channel name, paying a set lookup instead of a JSON parse. No state leaves the process, so recovery paths and loss windows are identical to the stock manager; acks and control messages stay on the shared channel and sio.call works across instances unchanged. This is the delivery scheme the official socket.io Redis adapter for Node.js ships by default.
Enabled by default; WEBSOCKET_REDIS_ROOM_CHANNELS=false restores shared-channel-only delivery. All instances must run the same mode, so the switch rides the full-stop upgrade this release already requires for its migration; in a mixed fleet, room emits from updated instances would not reach not-yet-updated ones. Verified end to end with two instances on a real Redis: cross-instance token streams delivered with the shared channel completely silent. Ref #28173.
<!--
🚨 DO NOT DELETE THE TEXT BELOW 🚨
Keep the "Contributor License Agreement" confirmation text intact.
Deleting it will trigger the CLA-Bot to INVALIDATE your PR.
Your PR will NOT be reviewed or merged until you check the box below confirming that you have read and agree to the terms of the CLA.
-->
- [x] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms.
> [!NOTE]
> Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.
With Chroma as the vector DB, hybrid search on a knowledge base with more than 32766 chunks fails with HTTP 400 "Error querying knowledge base". The legacy hybrid path fetches the whole collection to build the BM25 index, and Chroma's unbounded collection.get() binds one SQLite variable per row, so any collection above SQLite's 32766 variable limit raises "too many SQL variables" (reproduced on chromadb 1.5.9 with both PersistentClient and HttpClient). Vector-only search on the same collection works, which makes it look like a hybrid-search bug.
The Chroma adapter now reads the collection in pages of 10000 rows via limit/offset and concatenates them into the same GetResult shape as before.
Verified on a 90000-row collection: every row returned exactly once with documents and metadata aligned to ids, page order stable across page sizes, empty and exactly-one-page collections unchanged, and query_doc_with_hybrid_search returns results where it previously raised. The tests repo unit suite is identical before and after.
Fixes#30351
Generated images that come back as bare base64 (OpenAI b64_json, Gemini
bytesBase64Encoded and inlineData, Automatic1111) were always stored as
generated-image.png with content type image/png, even when the provider
returned JPEG or WebP, for example with {"output_format": "jpeg"} in the
OpenAI extra params. The image still rendered because browsers read the
bytes, but the download name, the served Content-Type and the type sent
along on a later image edit were wrong.
Bare base64 carries no format, so the type is now read from the bytes with
Pillow, the same way the file already inspects images elsewhere. A response
that is not an image at all now fails the generation instead of storing a
broken png. The file extension comes from the module's own extension map
first, because the Python 3.11 Docker image has no mime database entry for
WebP and would otherwise name the file generated-imageNone.
Fixes#29948
Every new saved chat logged "Error generating initial chat title" with a
KeyError: 'model' traceback. The title itself was already generated and
saved by then, so the log was misleading, and the memory settings played no
part in it. The error was silently logged at debug level since v0.10.0 and
became visible in v0.11.4 when the title path switched to log.exception.
The initial title task runs the shared background handler with a context
that has no resolved model, which the memory review step read
unconditionally. It now reads it with a default of None, which the memory
review already accepts. The title path never carries an assistant message,
so the review stops before doing any work there, and the main completion
path keeps reviewing memory exactly once per turn with the real model.
Fixes#30339
With the Docling content extraction engine, every conversion request
carried the server's full internal upload path (for example
/app/backend/data/uploads/<id>_report.pdf) as the multipart file name.
Docling only needs a bare file name, and a hosted Docling instance has
no business learning where Open WebUI keeps its files on disk.
The loader now sends the base name of the stored file, which is what
the MinerU, Datalab and Mistral loaders already do. Nothing else in
the request or the parsed result changes, verified against a capturing
mock server before and after.
Fixes#30352