Commit Graph

18379 Commits

Author SHA1 Message Date
Tim Baek
736707eeee Merge pull request #29282 from open-webui/dev
0.11.2
v0.11.2
2026-08-31 01:48:08 -04:00
Timothy Jaeryang Baek
471b5cbbb1 refac 2026-08-31 01:41:17 -04:00
Timothy Jaeryang Baek
a6f9751401 refac 2026-08-31 01:37:36 -04:00
Timothy Jaeryang Baek
6a2a131650 refac 2026-08-31 01:36:46 -04:00
Timothy Jaeryang Baek
2a4ef46ac8 refac 2026-08-31 01:29:36 -04:00
Timothy Jaeryang Baek
2daa610cba refac 2026-08-31 01:28:40 -04:00
Classic298
89716ea880 perf: stop scanning every socket.io payload for binary data (#28180)
* perf: stop scanning every socket.io payload for binary data

Every socket.io event the backend sends was first walked recursively to check whether any value was a bytes object needing binary attachment framing. Open WebUI never emits binary, so the walk always came back empty and the work was thrown away. It has no early exit and allocates at every level, so it scaled with the full size of the message, and the messages are the big ones: chat streaming re-emits the whole assistant message on every update, note collaboration sends document state as a JSON array with one entry per byte. With the Redis manager it ran once per instance per emit on top of that, since every instance builds its own copy of the packet.

The server now installs a Packet subclass with binary events off, through python-socketio's own serializer hook, the same mechanism its msgpack serializer uses. Inbound binary attachments are decoded to int lists rather than refused, so the one frontend path that sends a raw Uint8Array keeps working and handlers can still echo client data straight back out. One scan remains in multi-instance setups: python-socketio's Redis manager calls it on the base Packet class directly, where the serializer hook cannot reach.

Measured per encode:

| payload | before | after |
|---|---|---|
| chat completion re-emit (7.5 KB JSON) | 30 us | 13 us |
| collaborative document state (292 KB JSON) | 9.0 ms | 1.7 ms |

With ENABLE_ORJSON=true, where the scan is nearly the whole encode cost: 20 us to 2.3 us, and 7.8 ms to 0.14 ms.

Closes #28164

* fix: match the other Yjs emits and send the full state as an array

Collaboration.ts sent the initial full-document state as a raw Uint8Array while the other two Yjs emit sites convert with Array.from first. socket.io framed that one as a binary attachment, so with the JSON-only packet class the server turns it into a list of ints and re-broadcasts it as JSON: a 10240-byte state update becomes 36561 JSON characters. Converting at the emit site keeps the wire form uniform across all three sites.

Also trims the JSONOnlyPacket docstring, which claimed attachments already arrive as int lists when the override is what converts them, and annotates the new reconstruct_binary parameters.
2026-08-31 01:22:06 -04:00
Classic298
061f5e3a6d perf: stop re-parsing the whole tool-argument buffer on every streamed chunk (#28858)
* perf: stop re-parsing the whole tool-argument buffer on every streamed chunk

Converting an OpenAI stream to Anthropic events buffers each tool call's arguments and, to find out when the JSON is complete, parsed the entire buffer again on every chunk. A tool call with large arguments pays that parse thousands of times, and the cost grows with the square of the argument size.

The parse now runs only when the buffer could actually be complete. A JSON object can only close on its final brace, so a chunk that does not end there cannot complete it. Arguments that are not an object, or that start with whitespace, keep parsing on every chunk exactly as before.

Measured on CPython 3.12 with 130 KB of tool arguments over 7648 chunks:

| | before | after |
|---|---|---|
| parses | 7648 | 1 |
| time | 382 ms | 0.82 ms |

The block closes on exactly the same chunk as before, verified by replaying randomized fragmentations of objects with braces inside strings, escaped characters, unicode escapes, arrays, bare scalars, leading and trailing whitespace and a buffer that never completes, against both JSON backends.

* refactor: read tool['arguments'] directly in the JSON completion guard

Restores the pre-existing comment above the guard to its original wording and drops the `buffered` local, so the guard and the parse call both read `tool['arguments']`, the name the rest of the file already uses for that buffer. Behaviour is unchanged: same three conditions in the same order, same short-circuit result.

* perf: strip whitespace in the tool-argument completion guard

The character guard only looked at the first and last byte of the buffer, so a
chunk that ended in a space still triggered a full parse and a tool argument
with leading whitespace fell back to parsing on every chunk. Stripping first
collapses both cases to a single parse at the end of the stream.

Measured on a streamed tool call, parses and wall time for the whole stream,
orjson on the left of the slash and stdlib json on the right:

| argument shape | before | after |
|---|---|---|
| 20 KB string, char-by-char deltas | 3678 parses, 56 / 28 ms | 1 parse, 3.1 / 3.1 ms |
| 200 KB, 20-char deltas | 1473 parses, 176 / 63 ms | 1 parse, 3.1 / 2.8 ms |
| 8 KB prose, leading whitespace | 715 parses, 5.5 / 2.5 ms | 1 parse, 0.18 ms |
| 8 KB of spaces inside a value | 713 parses, 6.0 / 2.9 ms | 1 parse, 0.83 / 0.72 ms |

The strip costs about 20 ns per delta on arguments that have no whitespace at
either end, which is where the old form was already optimal: a 20 KB compact
argument goes from 191 to 216 us over 1786 deltas. Soundness is unchanged, the
guard can still only skip a parse that would have failed: 2660892 buffers
(exhaustive to length 6 over a JSON-lexical alphabet, every prefix of 26 named
cases with a trailing byte appended, and every codepoint below U+3000 after a
complete document) with zero cases where a parse would have succeeded.
2026-08-31 01:18:05 -04:00
Classic298
d7674c5174 perf: bounded non-blocking session pool reaper, fewer blocking pool round trips (#28835)
* perf: stop the Socket.IO session pool blocking the websocket event loop

With WEBSOCKET_MANAGER=redis the session pool is a synchronous Redis client, so every call into it blocks the whole worker's event loop, not just the caller. Two paths did it constantly: the orphan reaper walked the pool one round trip per session with no await anywhere, freezing the loop for the entire sweep every cycle, and nearly every socket event re-read the sender's session back out of Redis. Other users' events and every in-flight generation on that pod wait behind both.

The reaper now walks the pool in HSCAN batches and deletes in bulk, yielding between batches, and no longer sleeps past half the lock TTL, which previously guaranteed a failed renew every cycle. The per-event reads are gone: Socket.IO events only reach the worker holding the connection, and that worker already saved the same session dict locally when the user authenticated, so it was asking Redis for its own data. The writes stay, since those are what other pods read.

Measured at 4000 users / 16 containers, Redis 1.1 ms away:

| | before | after |
|---|---|---|
| reaper sweep, 5k sessions | 5.6 s, loop frozen throughout | 62 ms, 4.2 ms worst block |
| same, crash recovery with every session expired | 11.5 s | 96 ms |
| heartbeat / usage ping / disconnect | 2 / 3 / 2 round trips | 1 / 2 / 1 |
| 50-member channel post | 50 round trips, 57.2 ms block | 0 round trips, 0.02 ms |
| loop time per wall second at rest | 103 ms (10.3%) | 67 ms (6.7%) |

The alternative, converting RedisDict to the async client, fixes the same paths with a far larger blast radius (every call site gains await, and `in`/`[]`/`del` cannot be awaited so the dict interface goes) and still round-trips for data already in memory. Two deliberate behaviour changes: a heartbeat re-adds a session the reaper already removed, so a tab that survives a stall recovers instead of staying out of the pool until it reconnects; and disconnect no longer skips Yjs document cleanup when the pool entry is already gone, which previously leaked that document's update log forever.

Closes #28172

* perf: cut disconnect and user session lookup pool round trips, harden the session reaper

Follow-up on top of the session pool reaper branch. With WEBSOCKET_MANAGER=redis two paths still blocked the worker's event loop on synchronous Redis calls. Every disconnect listed all models in use cluster-wide and fetched each one individually, one blocking round trip per model. Disconnecting all sessions of a user (admin role change or deletion) pulled the entire session pool in one HGETALL and decoded every entry in a single uninterrupted block.

Disconnect now fetches the usage pool once with items(), going from 2+N+M round trips to 2+M (N models in use cluster-wide, M models the session used), and its delete of an emptied model entry is KeyError-guarded because another node can remove the same key between snapshot and delete; unguarded, that race aborted the handler and skipped its Yjs document cleanup. The user session lookup reuses the reaper's HSCAN batches and yields to the loop between pages. The reaper previously died permanently on the first Redis connection error, on every node at once during an outage; it now logs, releases the lock and returns to retrying acquisition.

* refac: keep the socket pool perf work to the round trips

A review pass on this branch turned up four changes riding along with the round-trip work without belonging to it, so they are backed out here. The `disconnect` handler keeps its `if sid in SESSION_POOL:` guard, so USAGE_POOL and ydoc cleanup stay off the path for sockets that never authenticated. `RedisDict.set()` keeps its own inline HDEL. `get_session_ids_by_user_id` stays synchronous over one HGETALL, since it runs on user delete and role change rather than per message. The crash-resilience wrapper around the reaper loop is dropped; if that guard is worth having, it belongs in its own change.

What stays is the perf part. The reaper now sweeps the pool in bounded HSCAN batches and deletes expired sids with one HDEL per batch, down from HKEYS plus an HGET and a per-sid HDEL across the whole pool. The `disconnect` handler reads USAGE_POOL with a single HGETALL, down from HKEYS plus one HGET per model in use. Session lookups in the socket handlers come from the local Socket.IO store, which removes one Redis GET from every heartbeat, usage, channel and ydoc event.

Naming and annotations follow the file: `get_session_pool_batches` for the module's `get_` prefix, `RedisDict.pop_many` so both reaper branches use one word for removing keys, a named `SCAN_BATCH_SIZE`, and types on the new helpers.

* fix: invalidate the RedisDict write signature on batch delete

RedisDict.set() skips the write when the payload fingerprint matches the last one this process wrote, so a mutation that goes around set() has to clear that fingerprint. The new batch delete did not, leaving a stale fingerprint behind: the next refresh with identical content is treated as already written and silently skipped, so the hash stays empty.

Renamed pop_many to delete_many. In a dict emulation pop removes and returns; this returns nothing and cannot without an extra HMGET, so the name promised something it does not do. delete_many matches __delitem__ and the HDEL underneath. Its only call site is the session pool reaper, whose behaviour is unchanged: same fields deleted, same batching, same return.
2026-08-31 01:17:53 -04:00
Classic298
ac6a8c0082 chore: changelog (#29107)
* chore: add changelog entries for 0.11.2

Documents the commits landed on dev after the 0.11.1 changelog entry. Added covers the richer terminal file previews with page thumbnails, the reduced per-message overhead on deployments without pipelines, more room in file previews on touch screens, and the wider accessibility coverage. Fixed covers twelve user-facing corrections, among them stalled streaming on reasoning models, post-tool-call thinking leaking into replies, banners with underlined text failing to render, pinned models carrying the previous model's tools, disabled admin models, skill-mention text loss, and the workspace Knowledge list staying empty. Changed records the rename of High Contrast Mode to Accessibility Mode. Also records the Polish, Simplified Chinese, German, Catalan, and Portuguese (Brazil) translation updates. Issue template, pull request template, Docker workflow and locale catalog regeneration edits are omitted as they are not user-facing.

* chore: add the Redis Cluster stop and Valves overflow entries to 0.11.2

Documents the two user-facing commits landed on dev since the previous
changelog entry. Fixed gains the Redis Cluster stop signal, where the stop
button did not take effect when the request landed on a different instance
than the one streaming the reply, placed with the streaming and thinking
entries it shares a domain with; and the Valves dialog overflow, where a
valve with a long line of selected options stretched its input past the
edge of the dialog and over the page behind it, placed with the narrow
screen layout entry.

The section date moves to 2026-08-29 to cover the newer commits. The issue
and pull request template wording and the German locale catalog are omitted,
the former as contributor-facing rather than user-facing, the latter as
German is already named in the translation entry.

* chore: add the recurring calendar event entries to 0.11.2

Documents the calendar recurrence changes landed on dev after the previous
changelog commit. Fixed records repeating events working out their occurrences
from their own date and time rather than from a start date carried inside the
repeat rule, which could place them on the wrong weekday or hour. Changed
records the new limit refusing events that repeat more often than once a day.
The EXRULE handling and the timezone resolution rewrite are omitted: both reach
the same user-visible outcome as before, only by a clearer route.

* chore: add the security advisory notice to 0.11.2

Adds the standard advisory notice as the first item in the Fixed section. The
calendar recurrence work landed on dev under an unmarked commit message and
bounds the occurrences a single stored event can force the server to walk, so
the release carries a fix whose details are not spelled out in the entries
below it. The notice is the fixed wording and takes no reference links of its
own; the individual entries keep theirs.

* chore: add the structured output crash entry to 0.11.2

Fixed records the conversation that failed in the browser and stopped showing
the assistant reply until a reload, together with the recovery of chats already
saved in that state. It sits directly below the advisory notice as the most
disruptive correction in the section. The Irish catalog update joins the
translation entry; the Portuguese (Brazil) pass needs no change there, as that
language is already named.

* chore: add the dropdown, SQLite search and tool server entries to 0.11.2

Fixed gains the dropdown that opened past the edge of a narrow screen and the
dropdown list that ignored the interface theme, kept together as one group, plus
case-insensitive matching for accented and non-Latin text on SQLite installs,
placed beside the existing SQLite entry, and the tool server connection that was
sent an empty authorization header when saved without a key.

The advisory notice already stands at the top of the section, so the unmarked
backend commit needs no further flag there.

* chore: add the chat reload entry to 0.11.2

Fixed records the conversation that reloaded itself whenever any response in it
finished while an older unfinished reply sat in the history, now narrowed to the
reply the update concerns. It joins the response lifecycle group below the stop
entry. The commit carries no pull request or issue, so it is referenced by
commit.

* chore: add the interface font and touch resize entries to 0.11.2

Added gains the font family field in Interface settings, which applies a locally
installed font across the interface and falls back to the standard font when
cleared, and the side panel divider that can now be dragged by touch or stylus
while a mouse drag keeps tracking beyond the window edge. Both sit above the
reserved accessibility, general improvements and translation entries, with the
touch entry beside the existing touch screen one.

Each aspect of the font setting arrived in a single commit, so it is recorded as
one entry with no separate note for its configurability.

* chore: add the automation schedule and model registry entries to 0.11.2

Added records the model list refresh that no longer has every worker rewrite the
whole list to the shared cache when nothing changed, placed beside the existing
performance entry.

Fixed records the two automation schedule defects from the same pull request as
separate entries, because the symptoms differ: a counted schedule shown as a
single run and rewritten to one on save, and a schedule carrying a start date
losing its weekly or monthly setting and printing raw rule text in the list.
Both join the scheduling group below the recurring event entry.

* chore: extend the touch resize entry to the main sidebar

The sidebar divider received the same pointer handling the side panel dividers
got, so the existing entry now names the sidebar and carries both commits rather
than repeating itself as a second entry. The follow-up that moved the divider
border to the matching edge is listed with them, being a further correction to
the same divider and too small to record on its own.

* chore: record the preview focus and caveat changes in 0.11.2

Fixed gains the arrow keys that paged an open document or slide preview from
anywhere on the page, which also took those keys away from the field being typed
in, now confined to the focused preview.

The richer previews entry absorbs the removal of the notice warning that a
preview might differ from the download, the caveat having gone with the
approximation it described, and the accessibility entry absorbs the previews
becoming reachable by keyboard and announcing themselves. Neither warranted an
entry of its own, both being continuations of work already recorded.
2026-08-31 01:14:29 -04:00
Timothy Jaeryang Baek
5d74df95ed chore: format 2026-08-31 01:11:41 -04:00
Classic298
b75e2670b7 fix: keep a custom recurrence rule when the editor reopens it (#29260)
Loading an automation whose rule the visual controls cannot represent switched the schedule to Custom but left the bookkeeping the seeding block reads on the previous value, so that block immediately replaced the rule with a freshly built default. The rule was lost when the editor opened, before anything was saved, and cloning carried the default across as well. Recording the switch alongside it leaves the stored rule in place.

Verified in a browser against the same build without this line: a minutely rule and a yearly rule now survive reopen and save byte for byte, cloning keeps the original, and every schedule the editor itself produces, along with switching to Custom by hand, behaves exactly as before.
2026-08-31 00:08:28 -05:00
Classic298
1976387808 fix: stop labelling a counted schedule as a one-off (#29261)
The schedule label treated any rule whose text contained COUNT=1 as a single run, so counts such as 10, 12 and 14 were shown as "Once" together with the date of the first run, on the automations list and on the automation page alike. The label now matches a count of exactly one.

This covers the two places that render the label. The schedule editor reads the count the same way and changes separately. Rules that carry a start date still fall through to the raw rule text, exactly as they already did without a count; that parsing gap changes separately too.

Verified in a browser against the same build without these lines: ten ordinary schedules render identically in both places, and a genuine single-run schedule is still labelled as one.
2026-08-31 00:08:01 -05:00
Timothy Jaeryang Baek
fd679e1dac refac 2026-08-31 01:06:15 -04:00
Classic298
9f680bb80b perf: skip the tool approval drain lookup for fresh chat messages (#29142)
Every chat completion request re-loads the target conversation's entire
message history from the database inside drain_approved_tool_calls() before
discovering there is nothing to drain: a fresh message always points at a
newly minted assistant message with no stored output, so the full-history
read (one SELECT of every chat_message row plus building the message map,
uncached, on top of the identical read process_chat_payload already did) is
pure overhead on every message.

Queued tool approvals can only ever be acted on by a resume or continue
request, and exactly those requests carry assistant_message_id in their
payload. The drain now returns early when the field is absent, removing one
O(conversation length) query per chat message while resume, continue, reject
and pause flows behave exactly as before, independent of the approval mode.
2026-08-30 23:57:11 -05:00
TOM
949876f9c0 fix(i18n): disable key/ns separator splitting at runtime to match i18next-parser config (#29161) 2026-08-31 00:54:08 -04:00
Timothy Jaeryang Baek
6609918bfe refac 2026-08-31 00:53:55 -04:00
Timothy Jaeryang Baek
9962d122c9 refac 2026-08-31 00:46:34 -04:00
Classic298
188fc83a79 fix: surface files the browser cannot read during a knowledge base directory sync (#29135)
* fix: surface files the browser cannot read during a knowledge base directory sync

Syncing a local folder into a knowledge base could fail with nothing but "Error accessing directory": no failing file name, no network request, no server log, and no console output either, because production builds strip console.error. On Windows this happens once the absolute path of a file passes the platform limit, at which point the browser refuses to open a file it just listed.

The directory scan now handles that per file. It names the first failing path and how many files are affected, and stops before anything is uploaded. Stopping is the point: a file missing from the manifest is treated as deleted by the sync, so continuing would remove the knowledge base copy of a file that still exists on disk.

Dragging a folder in hit the same failure and reported nothing at all, and the Firefox picker path returned its promise without awaiting it, so a rejection escaped the error handler and surfaced only as an unhandled rejection. Both report through the existing handler now, and production builds keep console.error so the underlying exception stays visible.

* fix: narrow the change to the silent drag-and-drop folder failure

Dropping a folder onto a knowledge base did nothing at all when the browser refused to open one of the files inside it: the rejection escaped the async drop listener, so the user got no toast, no upload and no clue why. The listener now routes that failure through the same error handler the directory picker already uses, so one path and one message cover both ways of adding a folder.

The rest of the branch is reverted. Dropping `console.error` from the esbuild `pure` list un-stripped 597 call sites across 103 files from every production bundle, which is a repo-wide logging policy change that needs its own argument. The picker-side collect-and-count machinery only reworded a toast the existing catch already showed, and the Firefox `return await` fix is a different bug in a different path.
2026-08-31 00:45:26 -04:00
Timothy Jaeryang Baek
8ed5487693 refac 2026-08-31 00:41:11 -04:00
Timothy Jaeryang Baek
1caf22b5a8 refac 2026-08-31 00:40:15 -04:00
Timothy Jaeryang Baek
873fb741c2 refac 2026-08-31 00:39:16 -04:00
Timothy Jaeryang Baek
b6d5055228 refac 2026-08-31 00:35:17 -04:00
Timothy Jaeryang Baek
e8bdbd716b refac 2026-08-31 00:33:43 -04:00
Timothy Jaeryang Baek
e96b6464b4 refac 2026-08-31 00:33:05 -04:00
Timothy Jaeryang Baek
7a11154182 refac 2026-08-31 00:32:46 -04:00
Classic298
be958d7b04 fix: a rejected ask_user call ending the turn with no reply (#29252)
* fix: a rejected ask_user call ending the turn with no reply

The documented behaviour of the built-in ask_user tool is that a call breaking its rules comes back to the model as an error. Instead the reply stopped there: the error was recorded as the tool result, the model was never asked again, and the user was left with a dead chat and no answer.

The rejection is now handed back like any other failed tool result, so the model sees it and can correct itself within the normal tool-call iteration limit. Any ordinary tool the model emitted in the same turn still runs.

A call rejected for arriving alongside other ask_user calls also left those siblings without a result, which the UI shows as a tool call stuck on "Executing..." forever. Every invalid call now gets its own result. Two ask_user calls on their own also reported the wrong reason, saying the call must be made by itself rather than that only one is allowed per turn.

Fixes #29077

* Keep the original ask_user validation order

Restores the pre-existing check order and the unchanged output id fallback, so this change only alters the return shape needed for staging, and trims a comment that narrated the lines below it.

* Correct the ask_user sibling-call error message

* Shorten the ask_user sibling-call error message

* Drop the untrue sibling-call claim from the ask_user error

The ask_user error text told the user and the model "The others ran.", but that sentence is written into the turn output before any sibling tool call has executed, so it can be plainly false. Under a saved chat with tool approval set to ask, the turn pauses right afterwards and the siblings sit at pending/queued, so the user reads "The others ran" directly above the approval prompt for tools that have not run, and reads it again beside the rejection result if they decline. When the model sends two ask_user calls and nothing else, nothing runs at all and the sentence is emitted twice.

The staging helper cannot see what happens to the sibling calls, so it no longer narrates it. The remaining two sentences hold in every flow: ask_user really is dropped from the executed calls whenever this error is set, and calling it on its own is always the right retry.
2026-08-31 00:17:21 -04:00
G30
95032b6c61 fix: let the model defaults capability and prompt suggestion sections scroll (#29235) 2026-08-31 00:11:34 -04:00
Timothy Jaeryang Baek
756241b34a refac 2026-08-31 00:11:13 -04:00
Timothy Jaeryang Baek
84d0940da1 refac 2026-08-31 00:11:04 -04:00
G30
9a669197c8 fix: let setting row controls shrink so long values do not squeeze the label (#29229) 2026-08-31 00:06:37 -04:00
Timothy Jaeryang Baek
09163ccc73 refac 2026-08-31 00:06:05 -04:00
Timothy Jaeryang Baek
2140c189e1 refac 2026-08-31 00:05:34 -04:00
Timothy Jaeryang Baek
81b9afb731 refac 2026-08-31 00:03:51 -04:00
Timothy Jaeryang Baek
64e6c9f010 refac 2026-08-30 23:56:01 -04:00
Timothy Jaeryang Baek
97c5f52bbc refac 2026-08-30 23:55:29 -04:00
Timothy Jaeryang Baek
e4dbfb1276 refac 2026-08-30 23:50:30 -04:00
Timothy Jaeryang Baek
aeb126b95d refac 2026-08-30 23:46:02 -04:00
Timothy Jaeryang Baek
df495a7945 refac 2026-08-30 23:42:35 -04:00
G30
039976ef24 fix: unregister a sidebar folder from the registry when it unmounts (#29121) 2026-08-30 23:35:34 -04:00
Timothy Jaeryang Baek
d5e35ea6f4 refac 2026-08-30 23:32:42 -04:00
Timothy Jaeryang Baek
6c2e0d3fe8 chore: format 2026-08-30 23:31:23 -04:00
Timothy Jaeryang Baek
7d694570aa refac 2026-08-30 21:47:28 -04:00
Timothy Jaeryang Baek
b3ba6823a9 refac
Co-Authored-By: G30 <50341825+silentoplayz@users.noreply.github.com>
2026-08-30 21:41:03 -04:00
Timothy Jaeryang Baek
ddc886fdc1 refac 2026-08-30 21:39:13 -04:00
Timothy Jaeryang Baek
49aab7451c refac 2026-08-30 21:36:17 -04:00
Classic298
d8133c905a fix: serve module scripts and wasm assets with the correct MIME type (#29139)
On Windows hosts the built-in code interpreter fails immediately with "Failed to fetch dynamically imported module: .../pyodide/pyodide.asm.mjs", and the browser console shows the server answered with a MIME type of "text/plain". Code execution is unusable for those users.

Python's mimetypes module reads the Windows registry after loading its own table, so a stray registry entry silently replaces the correct type for an extension and Starlette then labels the file with it. Browsers enforce strict MIME checking for module scripts and streaming WASM compilation, so the pyodide loader gets refused. The same workaround already existed for .js; this extends it to the two other extensions pyodide ships, and moves it out of the frontend-build branch so the unconditionally mounted /static assets are covered as well.

Fixes #29133
2026-08-30 21:32:31 -04:00
Timothy Jaeryang Baek
120409ef01 refac 2026-08-30 17:45:50 -04:00
Timothy Jaeryang Baek
e4694f82eb refac 2026-08-30 17:45:18 -04:00
Timothy Jaeryang Baek
b356b80f8c refac 2026-08-30 17:44:39 -04:00