Commit Graph
1732 Commits
Author SHA1 Message Date
Timothy Jaeryang Baek a6834f089b refac 2026-08-24 17:47:10 -04:00
Timothy Jaeryang Baek 91917b2395 refac 2026-08-24 17:16:01 -04:00
Timothy Jaeryang Baek 363ad352fe refac 2026-08-24 17:12:56 -04:00
Classic298 23b3a69bc2 fix: keep folder parent references acyclic (#28748)
Moving a folder under one of its own subfolders was accepted. A folder in a parent loop is never a root, so it and everything under it silently disappeared from the sidebar, and there was no way to get it back from the UI.

The move is now rejected with a 400, folders whose parent chain loops are put back at the root on the next folder list, and the folder tree traversals skip ids they have already visited so existing data in that state stays workable.
2026-08-24 17:06:19 -04:00
G30 6b4131d1d7 fix: log terminal proxy connectivity failures as single lines and handle client disconnects (#27755) 2026-08-24 07:35:38 -04:00
Classic298 9cf1a07960 fix: use the pooled client timeout for the Anthropic Messages passthrough (#27675)
* fix: use the pooled client timeout for the Anthropic Messages passthrough

The native `/api/v1/messages` passthrough still referenced `openai.AIOHTTP_CLIENT_TIMEOUT`, which stopped existing when `routers/openai.py` moved onto `session_pool.get_client_timeout()`. Every passthrough request therefore raised `AttributeError: module 'open_webui.routers.openai' has no attribute 'AIOHTTP_CLIENT_TIMEOUT'` before it was sent, and the surrounding handler turned that into a 502 "Open WebUI: Server Connection Error", so Anthropic-format clients such as Cline could not reach any model at all.

Use `get_client_timeout(stream=...)` like the OpenAI and Ollama proxies do, so the configured `AIOHTTP_CLIENT_TIMEOUT` applies and streaming requests additionally get the idle-read timeout.

Fixes #27595

* fix: authenticate native Anthropic requests with x-api-key

The Anthropic Messages passthrough and the token-count forwarding both build their upstream request through `get_anthropic_request_target`, which sends the connection key as `Authorization: Bearer <key>`. Anthropic's OpenAI-compatible `/chat/completions` endpoint accepts that, which is why the model works in the chat UI, but the native `/v1/messages` and `/v1/messages/count_tokens` endpoints do not: they require the key in `x-api-key` and reject a bearer token with 401 `Invalid bearer token` (and `jwt auth is not yet supported on count_tokens`). They also require an `anthropic-version` header, which was never sent.

For `api.anthropic.com` connections, send `anthropic-version` and move the key into `x-api-key`, dropping the bearer header. Connections using session, OAuth or Entra ID auth keep their token untouched, LiteLLM passthrough connections are unaffected, and admin-configured custom headers still win over both defaults.

Fixes #27695
2026-08-24 07:33:33 -04:00
Classic298 f73f09a3e0 refac: drop the redundant .keys() from two dict membership tests (#28859)
`x in d` and `x in d.keys()` are identical for a plain dict, so the `.keys()` call builds a throwaway view and reads as if it were doing something. Both sites operate on a plain dict: `combined` in `merge_and_sort_query_results` is a local `dict()`, and `ui_settings` comes from `UserSettings.model_dump()` where `ui` is annotated `dict | None` and is already guarded against None on the preceding line.

No behaviour change, and no measurable speedup either, so this is a readability cleanup rather than a performance one.

Sites where `.keys()` is load-bearing are left alone: the `list(d.keys())` snapshots taken before mutating during iteration, and the places where `.keys()` is the iteration or comprehension source rather than a membership test.
2026-08-23 16:02:09 -04:00
G30 603e85c569 fix: apply connection prefix id to the model display name as well as the id (#28950) 2026-08-23 15:27:09 -04:00
Timothy Jaeryang Baek fb4f476316 refac 2026-08-23 13:49:50 -04:00
Classic298 945c521ed2 refac: make the web search error message a plain constant (#28948)
The web search error message was a lambda with a passthrough branch that returned whatever it was handed. Since #28942 both call sites pass no arguments, so that branch is unreachable, and it is the trap that let a caller drop a raw exception object into an HTTP response body and turn an intended 400 into an unserialisable 500.

A plain string constant removes the trap and lines the message up with every other fixed message in that file. Behaviour is unchanged: the response detail comes out byte for byte identical, because the enum already overrides __str__ to render members as their value. Verified on Python 3.11 and 3.12, both producing the same string and the same JSON body.
2026-08-23 13:27:02 -04:00
Classic298 fca3be5416 fix: web search failures return HTTP 500 with an empty body instead of 400 (#28942)
Any failure during a web search comes back to the client as a bare HTTP 500 with nothing in it. The handler tries to build a 400 whose detail is the caught exception object itself, FastAPI cannot serialise that into a response body, so rendering the error response fails and the request falls through to the generic 500 handler. In chat this surfaces as a web search that fails with no explanation at all, and the most common trigger is simply selecting a search engine without configuring its API key.

This routes the failure through the standard error formatter, which is what the sibling handler for content loading failures in the same function already does. Web search failures now return 400 with a readable message, and the exception itself keeps going to the server log exactly as before.

Passing str(e) into the response was the other option and was rejected: the rest of the backend deliberately keeps provider exception text out of client responses and in the log, and provider exceptions here can carry request details that should not be echoed back.
2026-08-23 12:59:25 -04:00
Timothy Jaeryang BaekandClassic298 4807866a1c refac
Co-Authored-By: Classic298 <27028174+Classic298@users.noreply.github.com>
2026-08-23 01:31:30 -04:00
Timothy Jaeryang Baek 7d4747dfd7 refac 2026-08-20 13:13:51 -07:00
Timothy Jaeryang Baek 8a42aa53e8 refac 2026-08-19 22:48:32 -07:00
Classic298 4df2d9a7aa perf: filter workspace models by access in SQL instead of loading every model (#28795)
Exporting workspace models loaded every model row, built a full response object with its owner for each, and only then dropped the ones the caller may not see. On a large model table that made the export endpoint slow in proportion to models the user cannot even access.

The owner-or-grant check now happens in the query itself, reusing the permission filter this file already applies to the paginated list endpoint, so only visible rows are ever hydrated. The by-user wrapper had one caller left and is gone with it.

Measured with 500 workspace models of which 3 are visible to the caller: 5 queries and ~12.7 ms before, 4 queries and ~2.8 ms after. The resulting set is unchanged for owner, public, direct-user, group and multi-grant entries, and base model entries stay excluded as before.
2026-08-19 18:15:01 -07:00
Timothy Jaeryang Baek ea55d38793 refac 2026-08-19 16:14:27 -07:00
Classic298 7cf6051a74 perf: resolve group membership once per folder listing instead of once per entry (#28810)
Listing a user's folders re-checks which entries they may still see, and it resolved their group membership again for every folder, then again inside the collection and note branches for every entry. A comment in that helper claims one membership fetch for the whole listing, but the caller invokes it once per folder, so the claim never held.

The listing now resolves membership once, and only when some folder actually carries entries, then threads it through the file, collection and note checks. Callers that do not supply it are unchanged and still resolve for themselves.

Measured with twenty folders holding six files, two knowledge bases and two notes each: 245 queries and ~145 ms before, 186 and ~117 ms after. The folders returned, and the entries the integrity pass writes back, are unchanged. That was checked against entries the caller owns, entries shared through a group, entries shared with nobody, another user's files, and an unrecognised entry type.
2026-08-19 11:16:29 -07:00
Classic298 dbf715cb63 perf: stop scanning every skill on each listing and chat turn (#28798)
Listing skills ran one database query per skill in the instance. A non-admin opening the list on a workspace with 500 skills issued over 500 queries, the paginated list re-resolved the caller's group membership once per row, and every chat message carrying a skill loaded every skill the user can read, full body and owner included, to use the two or three it actually referenced.

Skills now arrive already filtered: the owner-or-grant check runs in the query as an EXISTS subquery, the same way prompts and the search endpoints already do it, the per-item write flag uses the existing batch grant lookup, and the chat path asks only for the skill ids the request names.

Measured with 500 skills of which 3 are visible to the caller: 504 queries and ~300 ms before, 4 queries and ~2.6 ms after. The resulting set is unchanged for owner, public, direct-user, group and multi-grant entries, for both read and write.
2026-08-19 11:07:33 -07:00
Classic298 6db64c4855 perf: batch the shared folder listing instead of fetching one folder at a time (#28804)
Opening the shared folder list fetched every shared folder in its own query, fetched a chunk of them a second time to walk their children, and looked up each distinct owner separately. With forty folders shared with a user that is over a hundred queries before any subtree work starts.

The folders and their owners now come back in one query each, and the inheritance pass reuses the rows already in hand. Both folder listings also gained an explicit order: the sidebar merges shared subfolders in response order without sorting them, and neither query had an ORDER BY, so on Postgres a folder rename could reshuffle its siblings.

Measured with forty shared folders and no subtrees: 181 queries and ~105 ms before, 92 and ~66 ms after. With subtrees attached, 203 folders in total, it is 341 queries before against 252 after; the remainder is the recursive child walk, which this change deliberately leaves alone. The returned set, permissions and owner names are unchanged, including for a grant pointing at a deleted folder row, a folder the caller owns that is also shared with them, a folder whose owner record is gone, and a child folder that is itself directly shared.
2026-08-19 11:06:37 -07:00
Classic298 21e390561d fix: revoke existing sessions when a password changes (#28725)
Changing a password left every other logged-in device working until the JWT expired on its own, up to four weeks with the default settings. The hardening docs already promise the opposite: with Redis configured a password change is supposed to put the user's tokens on the revocation list, but only sign-out and OIDC back-channel logout ever wrote to it.

Both password-change paths, self-service and an admin resetting someone's password, now stamp the per-user revocation marker that token validation already checks, so every session issued before the change stops working. The acting device is signed out as well and asked to sign in again, which is the safer default when the password is being changed precisely because the old one may be compromised. Without Redis nothing can be revoked, as before, and the backend now logs a warning saying so.

The marker is written through one shared helper, so its lifetime follows the configured JWT lifetime instead of a fixed 30 days and never expires at all when JWT_EXPIRES_IN disables expiry. Back-channel logout picks that up too, where a long or disabled JWT lifetime previously let the marker expire while the tokens it revoked were still valid. API keys keep working, they are separate credentials with their own lifecycle.

Discussed in #28647.
2026-08-17 13:56:29 -07:00
G30 88c55b86b1 feat: emit auth.login on SSO logins and attribute SSO logouts (#27619)
* feat: emit the auth.login event on SSO logins

* feat: attribute SSO logouts in the auth.logout event payload
2026-08-17 02:22:25 -06:00
Timothy Jaeryang Baek a3a81fee03 refac 2026-08-17 01:21:58 -07:00
Classic298 017075a2d7 perf: drop unused database session dependencies from seven endpoints (#28178)
Seven route handlers declare a request-scoped database session as a FastAPI dependency and then never touch it. Three of them are `GET /api/v1/users/user/settings`, `/user/status` and `/user/info`, which the frontend hits on every page load, and all three carry a comment saying the user object is already available, so the parameter is leftover from the refactor that removed the refetch. The other four are admin-only external-knowledge connection endpoints that read their data from the config store.

Measured on a route with and without the dependency, 20k requests, best of 5:

| | µs per request |
| --- | --- |
| no dependency | 16.18 |
| unused session dependency | 62.85 |

The dependency costs about three times as much as everything else the request does put together. It is worth being precise about why, because the obvious guess is wrong: this is not database I/O and not connection pool pressure. SQLAlchemy connects lazily, so a session that is never used checks out zero connections, verified by watching the pool's counter stay at zero across the request. The cost is FastAPI resolving an extra async-generator dependency onto the request's exit stack, plus constructing and closing the session object.

Deleting the seven parameters is the whole change. An AST scan over the backend finds exactly these seven handlers before and none after.
2026-08-17 01:53:00 -06:00
Timothy Jaeryang Baek 87d9b7e84e refac 2026-08-17 00:51:04 -07:00
Classic298 ba0c4b3932 fix: don't hold a database connection for the lifetime of an SSE stream (#28183)
With database session sharing enabled, which the docs recommend for PostgreSQL and for multi-replica deployments, the knowledge pending-files and file process-status endpoints each pinned one pooled connection for as long as their SSE stream stayed open, up to one and two hours respectively. A file wedged in processing keeps a stream open for the full duration, so a handful of users sitting on that page can consume every connection in the pool, and the held transactions sit idle and block autovacuum on those tables.

Both handlers took a request-scoped session for their access checks, and FastAPI only releases a yield dependency once the response body has finished streaming, so the session outlived the handler by the whole life of the stream. Neither generator ever used it. They no longer take that dependency, and the queries they run already open their own short-lived sessions when none is passed. This is the approach the chat completion endpoints already use for the same long-response problem.

Measured against a pool with capacity 11: before, at most 11 concurrent streams could ever be open and every further attempt failed, deterministically across repeat runs. After, 25 of 25 opened. Non-stream latency is unchanged, within run-to-run noise, and behaviour is identical whether session sharing is on or off.
2026-08-17 01:46:54 -06:00
Timothy Jaeryang Baek e968445812 refac 2026-08-17 00:43:47 -07:00
Timothy Jaeryang Baek d799e81edb refac 2026-08-17 00:42:16 -07:00
Timothy Jaeryang Baek ad8c79f686 refac 2026-08-17 00:18:35 -07:00
G30 8fc5ffe26e fix: persist the Open Sharing permission in default user permissions (#27609) 2026-08-17 01:03:07 -06:00
xyoniumandClaude 686d8dc54c fix: strip prefix id from model name in /responses endpoint (#28575)
The /openai/responses endpoint forwarded the prefixed model id (e.g.
"myprovider.gpt-4o") to the upstream provider instead of the stripped
native name, causing "model not found" errors when a connection has a
Prefix ID configured.

generate_chat_completion() already strips the prefix before forwarding;
apply the same strip_provider_model_prefix() call in responses() after
the urlIdx routing (which needs the prefixed id) and re-serialize the
body afterwards.

Also fixes the Azure non-v1 deployment path, which built the deployment
URL from the prefixed model name.

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-17 00:53:00 -06:00
Classic298 3df485582d fix: reject skill IDs that are not URL path safe (#27660)
A skill ID goes straight into the path of every mutating skill endpoint (/api/v1/skills/id/{id}/...), but create only replaced spaces with hyphens. An ID containing a "/" was stored verbatim as the primary key, so the route never matched, the request fell through to the SPA static mount and the client got 405 Method Not Allowed. The skill could not be opened, edited, toggled or deleted, by admins either, and since skill.name is UNIQUE it could not be recreated under a corrected ID. Percent-encoding does not help: uvicorn decodes the path before Starlette routes it, so the only remaining fix was a direct database write.

Create now rejects any ID outside [a-z0-9_-] with 400 instead of silently storing an unreachable one. Two frontend paths that fed unsanitized IDs into it are fixed as well: the manual "Skill ID" field, which was bound with no sanitization at all and is the path that reproduces on every version, and the markdown import, which put the raw frontmatter name into the ID before opening the editor in clone mode, where the reactive slugify is disabled.

Existing rows with an unreachable ID are not repaired here; rewriting a primary key would also have to re-point the access grants keyed on it.

Fixes #27655
2026-08-17 00:52:16 -06:00
Timothy Jaeryang Baek 954613944b refac 2026-08-16 23:51:38 -07:00
Timothy Jaeryang Baek 16f118d77a refac 2026-08-16 23:38:34 -07:00
Timothy Jaeryang Baek 1a376ac17f refac 2026-08-16 23:21:00 -07:00
Timothy Jaeryang Baek a1579a01ff refac 2026-08-14 00:22:17 -06:00
Timothy Jaeryang Baek 7d99b2716a refac 2026-08-13 19:59:11 -06:00
Timothy Jaeryang Baek 2c01d59335 refac 2026-08-13 17:26:38 -06:00
Timothy Jaeryang Baek 2649e3305c refac 2026-08-13 16:42:10 -06:00
Timothy Jaeryang Baek 85c3d0ae2f refac 2026-08-13 00:07:31 -06:00
Timothy Jaeryang Baek 9c21d4ed3b refac 2026-08-11 17:42:25 -06:00
Timothy Jaeryang Baek 4f9a0ebf71 refac 2026-08-11 17:35:05 -06:00
Timothy Jaeryang Baek f0bfcd4097 refac 2026-08-11 01:15:05 -06:00
G30 80d2f4154a fix: align public_tools and public_notes sharing defaults with config (#27716)
The SharingPermissions model defaulted public_tools and public_notes to
True while the config defaults (USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_PUBLIC_SHARING
and USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING) are both False.

On an instance whose stored user.permissions config predates these keys,
GET /api/v1/users/default/permissions fills the gap from the model and
reports both as enabled, while has_permission fills it from
DEFAULT_USER_PERMISSIONS and denies. Saving any unrelated permission then
persists the model's True, granting public tool and note sharing the admin
never enabled.
2026-08-10 23:24:18 -06:00
Timothy Jaeryang Baek ce3c175e26 refac 2026-08-10 23:13:10 -06:00
Timothy Jaeryang Baek 89922cc9d5 refac 2026-08-10 22:53:37 -06:00
Timothy Jaeryang Baek 2a6e671f54 refac 2026-08-10 22:47:39 -06:00
Timothy Jaeryang Baek c2107e5bb3 refac 2026-08-10 22:36:42 -06:00
Classic298andTim Baek a680f21e12 feat: make OAuth admin settings read-only when ENABLE_OAUTH_PERSISTENT_CONFIG is off (#28276)
When ENABLE_OAUTH_PERSISTENT_CONFIG is off (the default), oauth.* config is
never persisted and is read from environment variables, but the admin panel
still let admins edit the OAuth/OIDC fields and silently dropped every save on
restart, which kept confusing users who missed the docs warning
(open-webui/open-webui#28247).

The OAuth/OIDC section is now read-only in that case: the admin oauth config
endpoint reports the flag and the UI wraps the section in a disabled fieldset,
slightly dimmed with every control inert but all values still visible, plus a
note naming the env var. Saving skips the OAuth POST since nothing can change.
With the flag enabled the section behaves exactly as before.

Known limits: the guard is UI-side only (the POST endpoint keeps accepting
writes, unchanged), and disabled fields mean values cannot be selected and the
masked client secret cannot be revealed while read-only. Switch.svelte gains a
disabled:cursor-not-allowed style that applies to any disabled switch app-wide.

Co-authored-by: Tim Baek <tim@openwebui.com>
2026-08-10 21:41:07 -06:00
Timothy Jaeryang Baek a41faa3c22 refac 2026-08-10 20:00:43 -06:00
G30 121f2404ee fix(retrieval): report why a URL could not be read instead of blaming the knowledge base (#28362)
Fetching a URL and saving it were reported as one thing. Everything from
reading the URL to writing the vector database sat inside a single try,
whose handler blamed the knowledge base, so a page that could not be
fetched, parsed or resolved was reported as a knowledge base error even
though nothing had reached the knowledge base yet. Reading the URL now has
its own handler that names the URL, and the knowledge base message is left
to the step that actually touches it.

When YouTube refused a transcript the reason was discarded earlier still:
the loader caught the error, logged it, and returned an empty document
list, so the empty result failed downstream and even the salvageable
explanation was gone before a message was produced. The loader now raises
YoutubeTranscriptError carrying a readable reason, mapped from the
transcript library's own exception types. Blocked requests mention that a
proxy can be configured, and disabled, age restricted, unavailable and
missing language cases each say what actually happened.

URLs that attach successfully are unaffected.
2026-08-10 19:18:04 -06:00