* fix: replace bare except with except Exception in main.py
* fix: replace bare except with Exception in oauth.py
In Python 3, bare 'except:' is discouraged as it catches all
SystemExit and KeyboardInterrupt exceptions. Changed to 'except Exception:'
to only catch actual exceptions.
* feat: add LOG_FORMAT env var with JSON formatter for early logging
Introduce LOG_FORMAT environment variable (set to "json" to enable).
When active, logging.basicConfig() uses a JSONFormatter that outputs
single-line JSON objects with fields: ts, level, msg, caller, error,
stacktrace. This covers all log messages emitted during module imports
before Loguru's start_logger() takes over.
* feat: add JSON sink for Loguru when LOG_FORMAT=json
Add _json_sink() as a Loguru sink function that writes single-line JSON
to stdout. In start_logger(), conditionally use the JSON sink instead of
the plain-text stdout_format when LOG_FORMAT is set to "json".
* feat: suppress ASCII banner and fix alembic logging in JSON mode
- Wrap the ASCII art banner print in main.py with a LOG_FORMAT != "json"
guard so JSON output stays machine-parseable.
- Skip alembic's fileConfig() call in migrations/env.py when
LOG_FORMAT=json to prevent it from replacing the JSON log handlers
installed during early startup.
fix: model fallback routing for all model types and default model selection
Backend: When ENABLE_CUSTOM_MODEL_FALLBACK is active and a custom model's
base model is unavailable, the fallback now swaps the model and form data
to the configured default model directly. This ensures routing uses the
fallback model's type (pipe, Ollama, or OpenAI) instead of the original
model's type, which previously caused "Model not found" errors when the
fallback was a different backend type.
Frontend: Fixed default model selection in new chat initialization where
the admin-configured default models were always overwritten by the first
available model. The first-available fallback now only triggers when the
configured defaults don't resolve to valid available models.
* perf: eliminate 2 redundant full chat deserialization on every message send (#162)
Problem:
Every message send triggered get_chat_by_id_and_user_id which loads the
entire Chat row — including the potentially massive JSON blob containing
the full conversation history — even when the caller only needed a
simple yes/no ownership check or a single column value.
Two call sites in the message-send hot path were doing this:
1. main.py ownership verification: loaded the entire chat object including
all message history JSON, then checked `if chat is None`. The JSON blob
was immediately discarded — only the existence of the row mattered.
2. middleware.py folder check: loaded the entire chat object including all
message history JSON, then read only `chat.folder_id` — a plain column
on the chat table that requires zero JSON parsing.
Fix:
- Added `chat_exists_by_id_and_user_id()`: uses SQL EXISTS subquery which
returns a boolean without loading any row data. The database can satisfy
this from the primary key index alone.
- Added `get_chat_folder_id()`: queries only the `folder_id` column via
`db.query(Chat.folder_id)`, which tells SQLAlchemy to SELECT only that
single column instead of the entire row.
Both new methods preserve the same error handling semantics (return
False/None on exception) and user_id filtering (ownership check) as
the original get_chat_by_id_and_user_id.
Impact:
- Best case (typical): eliminates deserializing 2 full chat JSON blobs per
message send. For long conversations (hundreds of messages with tool
calls, images, file attachments), this blob can be multiple megabytes.
- Worst case: no regression — the new queries are strictly cheaper than
the old ones (less data transferred, less Python object construction,
no Pydantic model_validate overhead).
- The 3 remaining full chat loads in process_chat_payload (load_messages_from_db,
add_file_context, chat_image_generation_handler) are left untouched as
they genuinely need the full history and require separate analysis.
* Address maintainer feedback: rename method and inline call (#166)
- Rename chat_exists_by_id_and_user_id -> is_chat_owner
- Remove intermediate chat_owned variable; call is_chat_owner directly in if condition
* fix: prevent worker death during document upload by using run_coroutine_threadsafe
Replace asyncio.run() with asyncio.run_coroutine_threadsafe() in
save_docs_to_vector_db() to prevent uvicorn worker health check failures.
The issue: asyncio.run() creates a new event loop and blocks the thread
completely, preventing the worker from responding to health checks during
long-running embedding operations (>5 seconds default timeout).
The fix: Schedule the async embedding work on the main event loop using
run_coroutine_threadsafe(). This keeps the main loop responsive to health
check pings while the sync caller waits for the result.
Changes:
- main.py: Store main event loop reference in app.state.main_loop at startup
- retrieval.py: Use run_coroutine_threadsafe() instead of asyncio.run()
https://claude.ai/code/session_01UQSYvSTkXb57sFb7M85Kcw
* add env var
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix: add ScopedSession.remove() to prevent idle transaction leaks
The HTTP middleware was calling ScopedSession.commit() but not
ScopedSession.remove(), causing database connections to remain
"checked out" from the pool indefinitely. This resulted in
"idle in transaction" connections in PostgreSQL that could persist
for 30-50+ minutes.
With SQLAlchemy's scoped_session:
- commit() commits but keeps the session active
- remove() is required to return the connection to the pool
This fix adds the missing remove() call, ensuring connections are
properly returned after each HTTP request.
Also includes IDLE_TRANSACTION_ANALYSIS.md documenting the full
root cause analysis and additional recommendations.
* Delete IDLE_TRANSACTION_ANALYSIS.md
---------
Co-authored-by: Claude <noreply@anthropic.com>
- Add chat_message table for message-level analytics with usage JSON field
- Add migration to backfill from existing chats
- Add /analytics endpoints: summary, models, users, daily
- Support hourly/daily granularity for time-series data
- Fill missing days/hours in date range
* feat: add ENABLE_USER_STATUS toggle for admin-controlled user status visibility
feat: add ENABLE_USER_STATUS toggle for admin-controlled user status visibility
Add a new admin panel toggle (Admin > Settings > General) called "User Status" that allows administrators to globally enable or disable user status functionality.
When disabled:
- User status API endpoints return 403 Forbidden
- Status emoji, message, and "Update your status" button are hidden from the user menu
The setting:
- Defaults to True (enabled)
- Can be overridden via ENABLE_USER_STATUS environment variable
- Persists across restarts using PersistentConfig
Files modified:
- backend/open_webui/config.py - Added ENABLE_USER_STATUS PersistentConfig
- backend/open_webui/main.py - App state init and features dict
- backend/open_webui/routers/auths.py - AdminConfig model and endpoints
- backend/open_webui/routers/users.py - 403 guards on status endpoints
- src/lib/components/admin/Settings/General.svelte - Toggle UI
- src/lib/components/layout/Sidebar/UserMenu.svelte - Conditional status display
* Update UserMenu.svelte
feat: add ENABLE_USER_STATUS toggle for admin-controlled user status visibility
Add a new admin panel toggle (Admin > Settings > General) called "User Status" that allows administrators to globally enable or disable user status functionality.
When disabled:
- User status API endpoints return 403 Forbidden
- Active/Away indicator with blinking dot is hidden from the user menu
- Status emoji, message, and "Update your status" button are hidden from the user menu
The setting:
- Defaults to True (enabled)
- Can be overridden via ENABLE_USER_STATUS environment variable
- Persists across restarts using PersistentConfig
Files modified:
- backend/open_webui/config.py - Added ENABLE_USER_STATUS PersistentConfig
- backend/open_webui/main.py - App state init and features dict
- backend/open_webui/routers/auths.py - AdminConfig model and endpoints
- backend/open_webui/routers/users.py - 403 guards on status endpoints
- src/lib/components/admin/Settings/General.svelte - Toggle UI
- src/lib/components/layout/Sidebar/UserMenu.svelte - Conditional status display
* nuke the indicator
* fix