[GH-ISSUE #23950] issue: regression: Skills mentioned via $ in chat are never injected into the prompt in persisted chats #90861

Closed
opened 2026-05-15 16:08:24 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @Methraen on GitHub (Apr 21, 2026).
Original GitHub issue: https://github.com/open-webui/open-webui/issues/23950

Check Existing Issues

  • I have searched for any existing and/or related issues.
  • I have searched for any existing and/or related discussions.
  • I have also searched in the CLOSED issues AND CLOSED discussions and found no related items (your issue might already be addressed on the development branch!).
  • I am using the latest version of Open WebUI.

Installation Method

Git Clone

Open WebUI Version

v0.9.1

Ollama Version (if applicable)

No response

Operating System

Ubuntu 24.04.4

Browser (if applicable)

No response

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

When a user selects a skill via the $ autocomplete in the chat composer and sends a message, the skill's full content should be injected into the system prompt as <skill name="...">...</skill> — regardless of whether the chat is persisted or temporary.

Concretely, the frontend should include skill_ids: ["<skill-id>"] in the POST /api/chat/completions payload, which triggers the backend logic in backend/open_webui/utils/middleware.py (around lines 2468–2501) that resolves each skill and injects its content.

Actual Behavior

In a persisted (non-temporary) chat, skill_ids is never sent to the backend. The skill mention chip is correctly inserted in the composer and correctly serialized into the message content as <$skillId|label>, but the frontend fails to extract it before building the payload. As a result, the backend never injects the skill's content into the system prompt, and the model responds as if no skill were selected.

The feature works only when Temporary Chat is enabled.

Root cause — in src/lib/components/chat/Chat.svelte on main (commit 0a8a620f, tag v0.9.1):

// line ~2205
let messages = [ /* optional system prompt */ ].filter(Boolean);
if ($temporaryChatEnabled) {                          // line 2207
    messages = [...messages, ..._messages.map(/*...*/)];  // line 2210
    // ... role/content/image mapping
}                                                      // line 2253

// ...

// line 2267
const skillMentionRegex = /<\$([^|>]+)\|?[^>]*>/g;
const skillIds = [];
for (const message of messages) {                      // line 2270
    // never iterates user messages in persisted chats:
    // `messages` contains only the system prompt (or is empty).
}

// line 2323
skill_ids: skillIds.length > 0 ? skillIds : undefined,

This was introduced by commit 18fe1712 (2026-04-14, message refac), which moved _messages.map(...) inside if ($temporaryChatEnabled) without adapting the skill parsing block immediately below it. Before that commit (and since PR #21312 merged 2026-02-11), the array was populated unconditionally and the feature worked in all chat modes.

Steps to Reproduce

  1. Workspace → Skills → create a skill (e.g. name format-mail, with any formatting instruction as content). Ensure it is active.
  2. Start a new chat with Temporary Chat OFF (default).
  3. In the composer, type $ — the autocomplete menu opens and lists the skill.
  4. Select the skill (click or arrow keys + Enter) — a styled chip appears inline.
  5. Type a prompt and send.
  6. Open DevTools → Network and inspect the body of POST /api/chat/completions.

Observation: the request body has no skill_ids field (or skill_ids: undefined), and the model's response does not follow the skill's instructions.

Workaround: toggle Temporary Chat ON before step 2 — the skill is then injected and the model's response is correctly formatted.

Logs & Screenshots

Backend log (temporary log.warning added to backend/open_webui/utils/middleware.py at line 2469 for verification)

2026-04-21 13:26:04.451 | INFO     | "POST /api/chat/completions HTTP/1.1" 200
2026-04-21 13:26:04.472 | WARNING  | open_webui.utils.middleware:process_chat_payload:2469 - SKILL-DEBUG form_data.skill_ids=None
2026-04-21 13:26:04.472 | WARNING  | open_webui.utils.middleware:process_chat_payload:2471 - SKILL-DEBUG user_skill_ids=set()

→ confirms skill_ids is not present in the request body received by the backend.

Stored chat (from GET /api/v1/chats/{id}) — mention is preserved but was never extracted

{
    "chat": {
        "models": ["qwen3.5-27b"],
        "messages": [
            {
                "role": "user",
                "content": "<$format-mail|Format mail>\n** ******** ******* ** **** * *** ******* ******* ************ ** ** ***** **** ** ****** ***** ** ***** ******** **** *** ********"
            }
        ]
    }
}

(* 'cause I anonymized the user prompt)

The mention <$format-mail|Format mail> is correctly present in the stored user message content, confirming that:

  • the TipTap mention node was inserted properly,
  • the Turndown serializer (in RichTextInput.svelte) correctly emitted <$id|label>,
  • the skillMentionRegex in Chat.svelte should have matched it — but the regex was run against an empty/system-only messages array and never saw this content.

Database state (PostgreSQL)

SELECT id, name, is_active, length(content) FROM skill;
     id      |    name     | is_active | length
-------------+-------------+-----------+--------
 format-mail | Format mail | t         |    287

Skill is active and the user is its owner, so access control is not the cause.

Additional Information

No response

Originally created by @Methraen on GitHub (Apr 21, 2026). Original GitHub issue: https://github.com/open-webui/open-webui/issues/23950 ### Check Existing Issues - [x] I have searched for any existing and/or related issues. - [x] I have searched for any existing and/or related discussions. - [x] I have also searched in the CLOSED issues AND CLOSED discussions and found no related items (your issue might already be addressed on the development branch!). - [x] I am using the latest version of Open WebUI. ### Installation Method Git Clone ### Open WebUI Version v0.9.1 ### Ollama Version (if applicable) _No response_ ### Operating System Ubuntu 24.04.4 ### Browser (if applicable) _No response_ ### 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 When a user selects a skill via the `$` autocomplete in the chat composer and sends a message, the skill's full content should be injected into the system prompt as `<skill name="...">...</skill>` — regardless of whether the chat is persisted or temporary. Concretely, the frontend should include `skill_ids: ["<skill-id>"]` in the `POST /api/chat/completions` payload, which triggers the backend logic in `backend/open_webui/utils/middleware.py` (around lines 2468–2501) that resolves each skill and injects its content. ### Actual Behavior In a **persisted (non-temporary) chat**, `skill_ids` is never sent to the backend. The skill mention chip is correctly inserted in the composer and correctly serialized into the message content as `<$skillId|label>`, but the frontend fails to extract it before building the payload. As a result, the backend never injects the skill's content into the system prompt, and the model responds as if no skill were selected. The feature works only when **Temporary Chat** is enabled. **Root cause** — in `src/lib/components/chat/Chat.svelte` on `main` (commit `0a8a620f`, tag `v0.9.1`): ```js // line ~2205 let messages = [ /* optional system prompt */ ].filter(Boolean); if ($temporaryChatEnabled) { // line 2207 messages = [...messages, ..._messages.map(/*...*/)]; // line 2210 // ... role/content/image mapping } // line 2253 // ... // line 2267 const skillMentionRegex = /<\$([^|>]+)\|?[^>]*>/g; const skillIds = []; for (const message of messages) { // line 2270 // never iterates user messages in persisted chats: // `messages` contains only the system prompt (or is empty). } // line 2323 skill_ids: skillIds.length > 0 ? skillIds : undefined, ``` This was introduced by commit [`18fe1712`](https://github.com/open-webui/open-webui/commit/18fe1712) (2026-04-14, message `refac`), which moved `_messages.map(...)` inside `if ($temporaryChatEnabled)` without adapting the skill parsing block immediately below it. Before that commit (and since PR #21312 merged 2026-02-11), the array was populated unconditionally and the feature worked in all chat modes. ### Steps to Reproduce 1. Workspace → Skills → create a skill (e.g. name `format-mail`, with any formatting instruction as content). Ensure it is active. 2. Start a new chat with **Temporary Chat OFF** (default). 3. In the composer, type `$` — the autocomplete menu opens and lists the skill. 4. Select the skill (click or arrow keys + Enter) — a styled chip appears inline. 5. Type a prompt and send. 6. Open DevTools → Network and inspect the body of `POST /api/chat/completions`. **Observation:** the request body has no `skill_ids` field (or `skill_ids: undefined`), and the model's response does not follow the skill's instructions. **Workaround:** toggle Temporary Chat ON before step 2 — the skill is then injected and the model's response is correctly formatted. ### Logs & Screenshots ### Backend log (temporary `log.warning` added to `backend/open_webui/utils/middleware.py` at line 2469 for verification) ``` 2026-04-21 13:26:04.451 | INFO | "POST /api/chat/completions HTTP/1.1" 200 2026-04-21 13:26:04.472 | WARNING | open_webui.utils.middleware:process_chat_payload:2469 - SKILL-DEBUG form_data.skill_ids=None 2026-04-21 13:26:04.472 | WARNING | open_webui.utils.middleware:process_chat_payload:2471 - SKILL-DEBUG user_skill_ids=set() ``` → confirms `skill_ids` is not present in the request body received by the backend. ### Stored chat (from `GET /api/v1/chats/{id}`) — mention is preserved but was never extracted ```json { "chat": { "models": ["qwen3.5-27b"], "messages": [ { "role": "user", "content": "<$format-mail|Format mail>\n** ******** ******* ** **** * *** ******* ******* ************ ** ** ***** **** ** ****** ***** ** ***** ******** **** *** ********" } ] } } ``` (* 'cause I anonymized the user prompt) The mention `<$format-mail|Format mail>` is correctly present in the stored user message content, confirming that: - the TipTap mention node was inserted properly, - the Turndown serializer (in `RichTextInput.svelte`) correctly emitted `<$id|label>`, - the `skillMentionRegex` in `Chat.svelte` *should* have matched it — but the regex was run against an empty/system-only `messages` array and never saw this content. ### Database state (PostgreSQL) ``` SELECT id, name, is_active, length(content) FROM skill; id | name | is_active | length -------------+-------------+-----------+-------- format-mail | Format mail | t | 287 ``` Skill is active and the user is its owner, so access control is not the cause. ### Additional Information _No response_
GiteaMirror added the bug label 2026-05-15 16:08:24 -05:00
Author
Owner

@tjbck commented on GitHub (Apr 24, 2026):

Addressed in dev.

<!-- gh-comment-id:4311470487 --> @tjbck commented on GitHub (Apr 24, 2026): Addressed 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#90861