open-webui / open-webui/open-webui
bug: Orchestrator "Per chat" terminal context: opening HTML files returns 404 "File not found" — file preview requests cannot carry the terminal context (direct Open Terminal connections unaffected)
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 153k
- Forks
- 22.3k
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 204
Description
Before Submitting
- I searched open and closed issues and discussions for an existing report.
- I checked whether this is already fixed on the
devbranch or latest source. - I understand that maintainers want a well-written issue before any code pull request.
- I am using the latest available version of Open WebUI for my install method.
- This is not a security vulnerability.
Installation Method
Docker
Open WebUI Version
v0.11.3
Operating System
Debian 13
Browser
Chrome 153.0.4234.32
Ollama Version
No response
Summary
With an orchestrator-backed Open Terminal connection (Open WebUI → terminals → per-session open-terminal containers), setting Terminal Contexts → Chat = "Per chat" breaks opening HTML files from the terminal file browser / chat: the preview returns {"detail":"File not found"} — even when the file was created inside the same chat's terminal. Switching the setting back to "Shared" fixes it.
Two scoping notes:
- Only orchestrator connections are affected. A direct connection to a plain Open Terminal server does not have this problem, because direct connections have no Terminal Contexts — they always resolve to the same single workspace, so the request that fails here lands in the right place there.
- Observed with HTML files specifically. All other file operations I exercised (file browser listing, cwd tracking, model tool read/write, non-HTML file access) work correctly in Per chat mode. The failing request is
GET /p/<policy-id>/files/serve/<path>— the endpoint that exists to render HTML files (with their sibling assets) in an iframe.
Expected Behavior
Per the docs (Terminal Contexts):
Whichever mode you pick applies to the whole terminal: the model's tools, the file browser and the terminal panel all work in the same place.
Opening an HTML file from the file browser in Per chat mode should serve it from the chat's own context workspace.
Actual Behavior
Per chat mode: 404 for HTML files that exist in the chat's own context workspace. Orchestrator access log (files below verifiably exist inside their per-chat context dirs on the host):
GET /p/<policy-id>/files/serve/home/user/parrot-bicycle.html 404
GET /p/<policy-id>/files/serve/home/user/quail_bike.svg.html 404
GET /p/<policy-id>/files/serve/home/user/tank-battle.html 404
Steps to Reproduce
- Admin → Integrations → Open Terminal connection detected as orchestrator. Set Orchestrator → Terminal Contexts → Chat = "Per chat".
- Open a saved chat, open the terminal panel, ask the model to create an HTML file, e.g.
index.htmlin/home/user.- The file is created in the chat's own workspace: on the host at
.../terminals-data/terminals/<user-id>/contexts/<context-hash>/index.html - The file browser in the same chat lists it; model tools read/write it fine.
- The file is created in the chat's own workspace: on the host at
- Click the HTML file to open/preview it.
- Request:
GET /api/v1/terminals/<server_id>/p/<policy-id>/files/serve/home/user/index.html - Result:
{"detail":"File not found"}(HTTP 404) — the orchestrator log shows the 404 while the file verifiably exists in that chat's context directory.
- Request:
- Switch Terminal Contexts → Chat = "Shared" and repeat: the preview returns 200 and renders.
Logs, Screenshots, and Config
Orchestrator access log (404s for files that exist in their per-chat context dirs):
GET /p/<policy-id>/files/serve/home/user/parrot-bicycle.html "HTTP/1.1" 404
GET /p/<policy-id>/files/serve/home/user/quail_bike.svg.html "HTTP/1.1" 404
GET /p/<policy-id>/files/serve/home/user/tank-battle.html "HTTP/1.1" 404
Session container returns the 404 from view_file():
{"detail": "File not found"}
Additional Information
Root cause analysis
Why the context is lost on exactly this request:
- The preview URL for HTML files is built in the frontend as a plain URL (bound as the iframe/embed source):
${terminalApiBase}/files/serve/${path}— a navigation request cannot carry custom headers. - Every other file operation works in Per chat mode because those requests are
fetch()calls that send the browser'sx-session-idheader. Inbackend/open_webui/routers/terminals.py,proxy_terminal()derives and forwards the terminal context only when that header is present:
An iframe request has noheaders = {'X-User-Id': user.id} session_id = request.headers.get('x-session-id') if session_id: ... context_id = terminal_context_id(connection, {'chat_id': session_id}, 'chat') if context_id: headers[TERMINAL_CONTEXT_HEADER] = context_idx-session-id→ noX-Terminal-Context-Idis forwarded. - The terminals orchestrator's HTTP proxy (
routers/proxy.py) then falls back to thedefaultcontext (utils/context.py: empty context id normalizes toDEFAULT_CONTEXT_ID), i.e. it serves from the shared root workspaceterminals-data/terminals/<user-id>/. - In Per chat mode the file lives under
<user-id>/contexts/<context-hash>/, soview_file()in open-terminal (main.py,GET /files/serve/{path}) does not find it at the requested path →404 "File not found".
So the file is written through a context-aware path and read back through a context-blind path. In Shared mode both resolve to the same workspace, which is why the bug only appears with Per chat.
Why direct Open Terminal connections are unaffected: they have no contexts (per the docs, "Connections to a plain Open Terminal server have no contexts. They stay available everywhere and always resolve to the same workspace."), so the header-less iframe request still resolves to the one and only workspace.
Related observation (isolation)
The fallback lands on the default-context container, which mounts the parent directory containing all contexts/<hash>/ subdirs. Requesting /files/serve/home/user/contexts/<other-hash>/<file> through the same header-less path serves another chat's files — on orchestrator connections, per-chat workspace isolation is currently only path-visibility on this endpoint, not enforced.
Suggested fix direction
Since iframe/embed navigations cannot carry custom headers, the context has to travel in the URL:
- open-webui: include the chat context in the preview URL (e.g.
.../p/<policy-id>/files/serve/<path>?context_id=chat:<chat-id>or a dedicated path segment), and inproxy_terminal()translate it intoX-Terminal-Context-Idvia the existingterminal_context_id()helper. The chat id is known to the frontend at URL-build time. - terminals (optional counterpart): accept an explicit context override (query param) on the HTTP proxy route — mirroring what the WebSocket proxy route already does with its
context_idquery parameter (_context_from_websocket) — validated against theX-User-Id-scoped contexts. - Docs: until fixed, note under Terminal Contexts that Per chat mode breaks HTML preview/FileNav.
Environment
- Open WebUI:
v0.11.3(ghcr.io/open-webui/open-webui:main, build0a7c1583) - Terminals:
ghcr.io/open-webui/terminals:main(revisione15903af) - Open Terminal:
ghcr.io/open-webui/open-terminal:latest(revision72fb783b) - Orchestrator backend:
TERMINALS_BACKEND=docker, Docker 29.8.0, Docker Compose v5.5.1
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with backend/open_webui/routers/terminals.py, then trace the request through routers/proxy.py and utils/context.py; compare it with the HTML serving route in open-terminal's main.py. Reproduce the 404 with an orchestrator connection in Per chat mode and inspect how the frontend builds the iframe URL. Done means HTML previews resolve in the chat context while cross-chat file isolation remains intact.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker, python
- Domain
- api, backend, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100