fix: resolve valve priority for actions and filters via class instantiation
The priority sorting for action buttons and filter execution order
read valve data directly from the database JSON column using
Functions.get_function_valves_by_id(). This returns only explicitly
saved values — when a developer defines priority as a class default
in their Valves definition (e.g. priority: int = 5) without ever
opening the Valves UI to persist it, the database column remains
empty. Every function then resolves to priority 0, and the preceding
set() deduplication produces non-deterministic iteration order that
the stable sort preserves — resulting in random button placement on
every page load.
The fix instantiates the Valves class with database values as keyword
overrides: Valves(**(db_valves or {})). This merges any persisted
overrides onto the code-defined defaults, matching the pattern already
established in the action execution handler, filter processing
pipeline, and tool module initialization. A secondary sort key (the
function ID) ensures fully deterministic ordering even when multiple
functions share the same priority value.
Affected locations:
- get_action_priority in utils/models.py (action button ordering)
- get_priority in utils/filter.py (filter execution ordering)
feat: sort action buttons by valve priority
Action buttons under assistant messages were rendered in
non-deterministic order due to set() deduplication. They now
respect the priority field from function Valves, sorted ascending
(lower value = appears first, default 0), matching the existing
filter priority mechanism.
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.
This commit adds aria-labels to the text inputs and textareas that previously lacked them, applies role=switch to inputs, and adds accessible titles to floating quick actions.
This commit adds aria-labels to the search inputs, select fields, action buttons, and close buttons on modals across the admin users layout and the site changelog modal for improved screen reader support.
fix: reset taskIds and messageQueue on new chat
Fixes a bug where clicking "New Chat" after sending a message would
silently drop subsequent messages. The initNewChat function reset most
chat state but did not clear taskIds or messageQueue, causing
submitPrompt to queue messages indefinitely instead of sending them.
Adds critical accessibility fixes across various app components:
- auth/+page: provide alt text for logo, turn on screenReader support for password input, add aria-required, hide decorative SVGs from AT
- AppSidebar: wrap navigation icons in a <nav> structure, provide ARIA labels for Home and Chat icons
- s/[id]/+page: convert structural divs into semantically accurate h1 heading and time element, wrap message display in main region
- OnBoarding: replace flawed aria-labelledby with direct aria-label on start button
- NotificationToast: provide role='status' and aria-live='polite' for proper screen reader broadcasting
- ChangelogModal: add required heading semantics for structure
- AddFilesPlaceholder: provide heading element role for standalone text content
- ImportModal: provide aria-label for close button
Addresses WCAG 4.1.3, 1.1.1, 3.3.2, and 1.3.1.
Add aria-hidden='true' to 112 SVG icon components in src/lib/components/icons/ that were missing this attribute. Decorative icons that convey no semantic meaning should be hidden from the accessibility tree to prevent screen readers from attempting to read meaningless SVG markup (WCAG 1.1.1 Non-text Content, WCAG 4.1.2 Name, Role, Value).
The remaining 60 icon files already had aria-hidden='true' set. All 172 icon components now consistently declare aria-hidden='true' on their root svg element.
- Replace incorrect aria-roledescription='model-item' with role='option' and aria-selected on ModelItem.svelte. The previous attribute was not a valid ARIA role description and provided no useful information to screen readers.
- Add contextual aria-label to each model item button (e.g. 'Select GPT-4 model') instead of just the raw model name, making the action clear to screen reader users.
- Add role='listbox' and aria-label='Available models' to the scrollable model list container in Selector.svelte so screen readers announce the container's purpose and navigate items correctly.
- Make the model selector trigger button's aria-label dynamic: it now announces 'Selected model: GPT-4' when a model is selected, falling back to 'Select a model' when nothing is selected.
- Add aria-label to the eject (unload) button in ModelItem.svelte so screen readers announce its purpose.
- Add aria-label to the cancel download button in Selector.svelte with the specific model name being canceled.
- Improve model profile image alt text from generic 'Model' to contextual '{{modelName}} profile image'.
* 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
feat: add citation sources for fetch_url tool results
URL fetches now produce clickable citation sources in the UI, matching
the existing behavior of search_web and knowledge file tools. When a
model calls fetch_url during native tool calling, the fetched URL
appears as a citable source with a content preview, giving users full
transparency into what pages the model referenced.
* feat: add sortable columns to groups admin panel
Make the Group and Users column headers in the admin groups list clickable to sort groups alphabetically by name or numerically by member count. Clicking a column toggles ascending/descending order, indicated by a chevron icon. When no sort is active, the default API order (by updated_at) is preserved.
* Update Groups.svelte
* Update Groups.svelte
fix: gate model default features on global config and user permissions
If you disabled code interpreter globally and in user permissions but
enabled it as a default feature on a model, the code interpreter pill
still appeared in the chat input. Same issue for web search and image
generation.
The setDefaults function in Chat.svelte activated model default features
based solely on the model's capability flag, ignoring whether the feature
was globally enabled or allowed by user permissions. Added the same
global config and user permission checks already used by the integrations
menu visibility and the features object sent to the backend.
Previously loaded the entire ChatModel (including the full conversation JSON
blob) just to extract the title string. Now queries only the Chat.title
column directly, which is already a top-level DB column.
The GET /chats/shared endpoint was loading full Chat rows including
the entire conversation history JSON blob, only to discard it and
return SharedChatResponse (id, title, share_id, timestamps). Now
uses with_entities() to select only the 5 needed columns, avoiding
deserialization of potentially large chat JSON for every shared chat.
When models reference functions (via filterIds/actionIds) that no longer
exist in the database, the /api/models endpoint crashes with a 500 error,
preventing the UI from loading chats entirely. This can happen after
upgrades when built-in functions are removed or when user-created
functions are deleted while still referenced by models.
Instead of raising an exception, log at INFO level and skip the missing
function so the rest of the models load successfully.
Fixes#21464https://claude.ai/code/session_015JRM7m2bNeZPBBmV2Gv4Mj
Co-authored-by: Claude <noreply@anthropic.com>
The sharePublic prop in editor components (Knowledge, Tools, Skills,
Prompts, Models) incorrectly included an "|| edit" / "|| write_access"
condition, allowing users with write access to see and use the "Public"
sharing option regardless of their actual public sharing permission.
Additionally, all backend access/update endpoints only verified write
authorization but did not check the corresponding sharing.public_*
permission, allowing direct API calls to bypass frontend restrictions
entirely.
Frontend: removed the edit/write_access bypass from sharePublic in all
five editor components so visibility is gated solely by the user's
sharing.public_* permission or admin role.
Backend: added has_public_read_access_grant checks to the access/update
endpoints in knowledge.py, tools.py, prompts.py, skills.py, models.py,
and notes.py. Public grants are silently stripped when the user lacks
the corresponding permission.
Fixes#21356
Ensure chat_id is reliably passed to function pipelines/manifolds during internal task invocations (web search query generation, RAG query generation, image prompt generation).
This allows stateful functions to maintain per-chat state without fragmentation, as they will now receive a consistent chat_id for all chat-scoped invocations including internal tasks.
Backend changes:
- Pass chat_id in generate_queries call for web search
- Pass chat_id in generate_queries call for RAG/retrieval
- Pass chat_id in generate_image_prompt call
Frontend changes:
- Add optional chat_id parameter to generateQueries API function
- Add optional chat_id parameter to generateAutoCompletion API function
Fixes#20563
* 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: reduce TTFT by caching model lookups in chat completion
Skip expensive get_all_models() calls when models are already cached
in app.state. This significantly reduces Time To First Token (TTFT)
for chat completions and embeddings requests.
Previously, every request called get_all_models() which fetches model
lists from all configured backends. Now we check the cache first and
only call get_all_models() on cache miss.
Affected endpoints:
- openai: generate_chat_completion, embeddings
- ollama: embed, embeddings
Fixes#20069
Co-authored-by: Michael <42099345+mickeytheseal@users.noreply.github.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>
Pre-download NLTK punkt_tab during Docker build instead of at runtime.
This fixes document extraction failures in offline/airgapped environments
where the container cannot download the tokenizer data after restarts.
Fixes#21150
* feat(files): add shift+click quick delete to File Manager
Add shift+click functionality to FilesModal for rapid file deletion without confirmation dialogs.
Changes:
- Track Shift key state via keyboard event listeners
- When Shift is held, delete button bypasses confirmation and deletes immediately
- Visual feedback: delete icon turns red when Shift is held
- Optimized delete to remove file from local array instead of re-fetching entire list, enabling rapid successive deletions without UI flicker
This matches the quick delete pattern used in other workspace components like Tools, Prompts, and Models.
* Update FilesModal.svelte
## Summary
Eliminates redundant database query in update_memory_by_id_and_user_id. Previously, after modifying the memory object, it called get_memory_by_id which opened a new session and queried again.
## Changes
models/memories.py update_memory_by_id_and_user_id:
- Replace self.get_memory_by_id(id) with db.refresh(memory)
- Return the same memory object that was already modified
## Performance Impact
Before: 2 queries (get + get_memory_by_id)
After: 1 query + refresh on same session
fix: add lang="ts" to Svelte script tags using
TypeScript syntax
Two Svelte components were using TypeScript type annotations (ReturnType<typeof setTimeout>)
but their script tags were missing the lang="ts" attribute. This caused Vite/Svelte to parse
them as plain JavaScript, resulting in parse errors.
Fixed files:
- src/lib/components/admin/Users/UserList.svelte
- src/lib/components/channel/ChannelInfoModal/UserList.svelte
fix: remove invalid expunge call on Pydantic FileModel
Files.get_file_by_id() returns a Pydantic FileModel, not an SQLAlchemy
ORM object. Calling db.expunge() on a Pydantic model fails with
UnmappedInstanceError since it lacks _sa_instance_state.
The expunge was also unnecessary because subsequent DB updates already
use fresh sessions via get_db() context manager.
Fixes#20925
Remove Depends(get_session) from POST /query endpoint to prevent database connections from being held during embedding API calls (1-5+ seconds).
The Memories.get_memories_by_user_id() function manages its own short-lived session internally, releasing the connection before the slow EMBEDDING_FUNCTION() call begins.
Remove Depends(get_session) from POST /create endpoint to prevent database connections from being held during embedding API calls (1-5+ seconds).
The has_permission() and Knowledges.insert_new_knowledge() functions manage their own short-lived sessions internally, releasing connections before the slow embed_knowledge_base_metadata() call begins.
Remove Depends(get_session) from POST /{id}/update endpoint to prevent database connections from being held during embedding API calls (1-5+ seconds).
All database operations (get_knowledge_by_id, has_access, has_permission, update_knowledge_by_id, get_file_metadatas_by_id) manage their own short-lived sessions internally, releasing connections before and after the slow embed_knowledge_base_metadata() call.
Remove Depends(get_session) from the /v1/completions endpoint to prevent database connections from being held during the entire duration of LLM calls.
Previously, the database session was acquired at request start and held until the response completed. Under concurrent load, this exhausted the connection pool, causing QueuePool timeout errors.
The fix allows Models.get_model_by_id() and has_access() to manage their own short-lived sessions internally, releasing the connection immediately after authorization checks complete.
Remove Depends(get_session) from the /v1/chat/completions endpoint to prevent database connections from being held during the entire duration of LLM calls.
Previously, the database session was acquired at request start and held until the streaming response completed. Under concurrent load, this exhausted the connection pool, causing QueuePool timeout errors.
The fix allows Models.get_model_by_id() and has_access() to manage their own short-lived sessions internally, releasing the connection immediately after authorization checks complete.
Remove Depends(get_session) from the /api/chat endpoint to prevent database connections from being held during the entire duration of LLM calls (30-60+ seconds for streaming responses).
Previously, the database session was acquired at request start and held until the streaming response completed. Under concurrent load, this exhausted the connection pool, causing QueuePool timeout errors for other database operations.
The fix allows Models.get_model_by_id() and has_access() to manage their own short-lived sessions internally, releasing the connection immediately after the quick authorization checks complete - before the slow external LLM API call begins.
Remove Depends(get_session) from the /chat/completions endpoint to prevent database connections from being held during the entire duration of LLM calls (30-60+ seconds for streaming responses).
Previously, the database session was acquired at request start and held until the streaming response completed. Under concurrent load, this exhausted the connection pool, causing QueuePool timeout errors for other database operations.
The fix allows Models.get_model_by_id() and has_access() to manage their own short-lived sessions internally, releasing the connection immediately after the quick authorization checks complete - before the slow external LLM API call begins.
Remove Depends(get_session) from POST /add endpoint to prevent database connections from being held during embedding API calls (1-5+ seconds).
The Memories.insert_new_memory() function manages its own short-lived session internally, releasing the connection before the slow EMBEDDING_FUNCTION() call begins.
Remove Depends(get_session) from POST /metadata/reindex endpoint to prevent database connections from being held during N embedding API calls.
This endpoint is CRITICAL as it loops through ALL knowledge bases and calls embed_knowledge_base_metadata() for each one. With the original code, a single connection would be held for the entire duration (potentially minutes for large deployments), completely exhausting the pool.
The Knowledges.get_knowledge_bases() function manages its own short-lived session, releasing the connection before the embedding loop begins.
Remove Depends(get_session) from POST /process/files/batch endpoint to prevent database connections from being held during batch embedding API calls (5-60+ seconds for large batches).
The save_docs_to_vector_db() function makes external embedding API calls. Post-embedding file updates (Files.update_file_by_id) manage their own short-lived sessions internally, releasing connections promptly.
Remove Depends(get_session) from POST /reset to prevent catastrophic connection pool exhaustion.
This endpoint was holding a SINGLE database connection while executing N PARALLEL embedding API calls via asyncio.gather(). For a user with 100 memories, this meant one connection blocked for potentially MINUTES (100 calls * 1-5 seconds each, even in parallel due to rate limits).
A single user triggering /reset could completely starve the connection pool, causing QueuePool timeout errors across the entire application.
The Memories.get_memories_by_user_id() function now manages its own short-lived session, releasing the connection immediately before the massive parallel embedding operation begins.
fix: release database connections immediately after auth instead of holding during LLM calls
Authentication was using Depends(get_session) which holds a database connection
for the entire request lifecycle. For chat completions, this meant connections
were held for 30-60 seconds while waiting for LLM responses, despite only needing
the connection for ~50ms of actual database work.
With a default pool of 15 connections, this limited concurrent chat users to ~15
before pool exhaustion and timeout errors:
sqlalchemy.exc.TimeoutError: QueuePool limit of size 5 overflow 10 reached,
connection timed out, timeout 30.00
The fix removes Depends(get_session) from get_current_user. Each database
operation now manages its own short-lived session internally:
BEFORE: One session held for entire request
──────────────────────────────────────────────────
│ auth │ queries │ LLM wait (30s) │ save │
│ CONNECTION HELD ENTIRE TIME │
──────────────────────────────────────────────────
AFTER: Short-lived sessions, released immediately
┌──────┐ ┌───────┐ ┌──────┐
│ auth │ │ query │ LLM (30s) │ save │
│ 10ms │ │ 20ms │ NO CONNECTION │ 20ms │
└──────┘ └───────┘ └──────┘
This is safe because:
- User model has no lazy-loaded relationships (all simple columns)
- Pydantic conversion (UserModel.model_validate) happens while session is open
- Returned object is pure Pydantic with no SQLAlchemy ties
Combined with the telemetry efficiency fix, this resolves connection pool
exhaustion for high-concurrency deployments, particularly on network-attached
databases like AWS Aurora where connection hold time is more impactful.
fix: use efficient COUNT queries in telemetry metrics to prevent connection pool exhaustion
This fixes database connection pool exhaustion issues reported after v0.7.0,
particularly affecting PostgreSQL deployments on high-latency networks (e.g., AWS Aurora).
## The Problem
The telemetry metrics callbacks (running every 10 seconds via OpenTelemetry's
PeriodicExportingMetricReader) were using inefficient queries that loaded entire
database tables into memory just to count records:
len(Users.get_users()["users"]) # Loads ALL user records to count them
On high-latency network-attached databases like AWS Aurora, this would:
1. Hold database connections for hundreds of milliseconds while transferring data
2. Deserialize all records into Python objects
3. Only then count the list length
Under concurrent load, these long-held connections would stack up and drain the
connection pool, resulting in:
sqlalchemy.exc.TimeoutError: QueuePool limit of size 5 overflow 10 reached,
connection timed out, timeout 30.00
## The Fix
Replace inefficient full-table loads with efficient COUNT(*) queries using
methods that already exist in the codebase:
- `len(Users.get_users()["users"])` → `Users.get_num_users()`
- Similar changes for other telemetry callbacks as needed
COUNT(*) queries use database indexes and return a single integer, completing in
~5-10ms even on Aurora, versus potentially 500ms+ for loading all records.
## Why v0.7.1's Session Sharing Disable "Helped"
The v0.7.1 change to disable DATABASE_ENABLE_SESSION_SHARING by default appeared
to fix the issue, but it was masking the root cause. Disabling session sharing
causes connections to be returned to the pool faster (more connection churn),
which reduced the window for pool exhaustion but didn't address the underlying
inefficient queries.
With this fix, session sharing can be safely re-enabled for deployments that
benefit from it (especially PostgreSQL), as telemetry will no longer hold
connections for extended periods.
## Impact
- Telemetry connection usage drops from potentially seconds to ~30ms total per
collection cycle
- Connection pool pressure from telemetry becomes negligible (~0.3% utilization)
- Enterprise PostgreSQL deployments (Aurora, RDS, etc.) should no longer
experience pool exhaustion under normal load
Refactored the file processing status streaming endpoint to avoid holding
a database connection for the entire stream duration (up to 2 hours).
Changes:
- Each status poll now creates its own short-lived database session instead
of capturing the request's session in the generator closure
- Increased poll interval from 0.5s to 1s, halving database queries with
negligible UX impact
This prevents a single file status stream from blocking a connection pool
slot for hours, which could contribute to pool exhaustion under load.