Setup: Ubuntu 24.04, Docker Compose, Open WebUI :main (v0.11.0) + Open Terminal (0.11.34), OpenAI API, bind-mounted sandbox so files land on my host.
[UPDATE] I added a detailed comment below that outlines where I was coming from, with a :8000 port busy due to an HP printer. Hopefully that shows where I was confused. Our assessment is that there's an issue, but maybe we're wrong?
Claude believes the following:
We weren't simply careless. The documentation's Compose file and its connection instructions don't work together.
The installation page shows a Compose file with both services in Docker and no published ports
The connecting page tells you to add the connection through Admin Settings
In that combination, the UI route cannot reach the service. The instructions appear to assume Open Terminal running on your host rather than in a container — which is what both published practitioner writeups (that we could find) actually do:
One runs it bare-metal so the model can administer a Proxmox host
One runs it locally against a hosted Open WebUI frontend
We hadn't found a writeup for the both-in-Docker case, which is why it had to be worked out.
Working now — code execution and the Files sidebar, both from one connection, on GPT-5.6 Terra. The main thing worth sharing: connection records persist through UI deletion, and the frontend keeps using the old one, so a correct config change looks like it did nothing.
I initially concluded this was an architectural limitation — that the sidebar and execution needed mutually incompatible URLs. It isn't. Every config change I made was being applied on top of a stale record that the frontend kept selecting.
The working config
services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: unless-stopped
ports:
- "3000:8080"
volumes:
- ./owui-data:/app/backend/data
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY}
- WEBUI_AUTH=true
- WEBUI_ADMIN_EMAIL=${WEBUI_ADMIN_EMAIL}
- WEBUI_ADMIN_PASSWORD=${WEBUI_ADMIN_PASSWORD}
- ENABLE_SIGNUP=false
# cost control — background calls (titles/tags/follow-ups) resend the
# whole conversation, so route them to the cheapest model
- TASK_MODEL_EXTERNAL=gpt-5.6-luna
- ENABLE_FOLLOW_UP_GENERATION=false
- ENABLE_TAGS_GENERATION=false
# silences host.docker.internal:11434 probe errors
- ENABLE_OLLAMA_API=false
# the terminal connection — this is the part that matters
- >-
TERMINAL_SERVER_CONNECTIONS=[{
"id": "myterminal",
"name": "My Terminal",
"enabled": true,
"url": "http://open-terminal:8000",
"key": "${OPEN_TERMINAL_API_KEY}",
"auth_type": "bearer",
"config": {
"access_grants": [{
"principal_type": "user",
"principal_id": "*",
"permission": "read"
}]
}
}]
open-terminal:
image: ghcr.io/open-webui/open-terminal
container_name: open-terminal
restart: unless-stopped
volumes:
- ./sandbox:/home/user # bind mount — files land on the host
# NO ports: — everything goes over the Compose network
environment:
- OPEN_TERMINAL_API_KEY=${OPEN_TERMINAL_API_KEY}
- OPEN_TERMINAL_EXECUTE_DESCRIPTION=Python 3.12, Node.js, git, ffmpeg, build tools installed.
deploy:
resources:
limits:
memory: 4G
cpus: "4.0"
.env alongside it holds OPENAI_API_KEY, WEBUI_SECRET_KEY, OPEN_TERMINAL_API_KEY, WEBUI_ADMIN_EMAIL and WEBUI_ADMIN_PASSWORD.
Model config: model enabled in Admin → Models and set as default, Code Interpreter on, and — for GPT-5.6 — Reasoning Effort Custom → none (see the tools 400 below).
Verified on this config:
gpt-5.6-terra executing run_command / get_process_status, with the Files sidebar listing /home/user in the same conversation
write_file / list_files writing to the sandbox (tested on gpt-3.5-turbo before I re-enabled Terra), with files appearing immediately in the bind-mounted ./sandbox on the host
Two things that actually matter in that config
Use a path-safe id
I initially set "id" to the URL. The Files sidebar then requests:
GET /api/v1/terminals/http://open-terminal:8000/files/list?directory=%2F
→ 404 {"detail":"Not Found"}
The terminal ID is interpolated unencoded into the API path, so a URL-shaped ID puts // and : inside the route and it can't match. (The query parameter is encoded — directory=%2F — just not the path segment.)
Caveat on causation: I changed the id to myterminal and wiped the database, so I can't say which was decisive — I never tested a clean database with a URL-shaped id. The 404 above is real and reproducible regardless, and the unencoded path segment looks like a bug either way.
Worth noting the sidebar proxies through Open WebUI's own API rather than calling Open Terminal directly, which surprised me — Sec-Fetch-Site: same-origin.
The full JSON schema is required
A minimal [{"url": ..., "key": ...}] was silently ignored — no error, no connection, nothing in the picker. access_grants in particular seems to be required.
I found the schema on the Terminals (orchestrator) page, not the Open Terminal connecting page where I'd been looking.
Diagnostic traps
Stale connection records survive UI deletion
After a few iterations I hit:
RuntimeError: Terminal server '301ea422-11c5-43a8-9404-48cbbcf3ac5e' is unavailable
File "/app/backend/open_webui/utils/tools.py", line 1339, in get_terminal_tools
A database UUID — not any ID I'd configured. Deleting connections through the UI didn't clear it, and the frontend kept sending the old identifier. A HAR capture confirmed it was still requesting the previous URL-as-ID after I'd changed the config and recreated the container.
So I'd change something, test, see the same failure, and conclude the change was wrong — when it had simply never been used.
docker compose down
sudo rm -rf owui-data/* # sudo needed — container writes as root
docker compose up -d
Everything worked immediately after.
If you've iterated on terminal connections at all, wipe before drawing conclusions.
Note the sudo — the embedding cache, uploads and vector_db/chroma.sqlite3 are root-owned even through a bind mount.
Terminal errors are sticky per conversation
Once a chat shows Terminal unavailable, it stays broken. Fixing the connection or switching selection doesn't recover it. Start a new conversation before retesting anything.
Model settings behave the same way — change a param, test in the same chat, and you're silently testing the old config.
Between these two, a lot of my "that didn't work" conclusions were about stale state rather than the change I'd just made.
Things that looked necessary but weren't
I added all of these while chasing the problem, then removed them one at a time afterwards. None were needed:
- Publishing Open Terminal's port to the host. I'd assumed the Files sidebar called Open Terminal from the browser. It doesn't — it proxies through Open WebUI's API.
- An
/etc/hosts entry mapping open-terminal to 127.0.0.1, so one hostname would resolve from both host and container.
- A DIRECT (UI) connection alongside the env-var one. This one actively hurt — it's what left the stale record behind.
Removing all three changed nothing. The Open Terminal log shows every request arriving from the open-webui container (172.18.0.3), never from the host bridge (172.18.0.1). The docs' portless example is correct — everything goes over the Compose network.
Separate finding: GPT-5.6 + tools → 400
Not related to the above, and I couldn't find it reported here:
Function tools with reasoning_effort are not supported for gpt-5.6-terra
in /v1/chat/completions. To use function tools, use /v1/responses or set
reasoning_effort to 'none'.
LibreChat, LiteLLM, LangChain, Cline and RubyLLM have all filed this. Open WebUI is affected too.
Workaround: Admin → Models → edit model → Advanced Params → Reasoning Effort: Custom → none → Save & Update, then a new conversation. Confirmed working — Terra executes tools normally with this set.
Cost: you lose GPT-5.6's reasoning. OpenAI's other suggested remedy is /v1/responses — I couldn't find a way to select that in the UI.
One smaller note: I'd disabled most of the ~116 OpenAI models to declutter the picker, and requests then fell through to gpt-3.5-turbo rather than erroring. A database wipe resets these toggles, so check after any reset.
Suggestions
-
URL-encode the terminal ID in the Files sidebar request path, or reject URL-shaped IDs at registration. Right now TERMINAL_SERVER_CONNECTIONS accepts an id of http://open-terminal:8000 without complaint, and the resulting request path is unparseable.
-
Surface an error when TERMINAL_SERVER_CONNECTIONS is malformed. An incomplete schema currently does nothing at all, which is hard to debug.
-
Document the schema on the Open Terminal connecting page. It's only on the Terminals orchestrator page.
-
Make UI deletion actually clear the record, or expose what's registered. The mismatch between deleted-in-UI and still-in-database is what made everything else hard to diagnose.
Question
Where is the Responses API toggle? It's OpenAI's recommended fix for the GPT-5.6 tools error and would preserve reasoning, unlike reasoning_effort: none. I couldn't find it in the UI or the env var reference.
Setup: Ubuntu 24.04, Docker Compose, Open WebUI
:main(v0.11.0) + Open Terminal (0.11.34), OpenAI API, bind-mounted sandbox so files land on my host.[UPDATE] I added a detailed comment below that outlines where I was coming from, with a :8000 port busy due to an HP printer. Hopefully that shows where I was confused. Our assessment is that there's an issue, but maybe we're wrong?
Claude believes the following:
We weren't simply careless. The documentation's Compose file and its connection instructions don't work together.
The installation page shows a Compose file with both services in Docker and no published ports
The connecting page tells you to add the connection through Admin Settings
In that combination, the UI route cannot reach the service. The instructions appear to assume Open Terminal running on your host rather than in a container — which is what both published practitioner writeups (that we could find) actually do:
One runs it bare-metal so the model can administer a Proxmox host
One runs it locally against a hosted Open WebUI frontend
We hadn't found a writeup for the both-in-Docker case, which is why it had to be worked out.
Working now — code execution and the Files sidebar, both from one connection, on GPT-5.6 Terra. The main thing worth sharing: connection records persist through UI deletion, and the frontend keeps using the old one, so a correct config change looks like it did nothing.
I initially concluded this was an architectural limitation — that the sidebar and execution needed mutually incompatible URLs. It isn't. Every config change I made was being applied on top of a stale record that the frontend kept selecting.
The working config
.envalongside it holdsOPENAI_API_KEY,WEBUI_SECRET_KEY,OPEN_TERMINAL_API_KEY,WEBUI_ADMIN_EMAILandWEBUI_ADMIN_PASSWORD.Model config: model enabled in Admin → Models and set as default, Code Interpreter on, and — for GPT-5.6 — Reasoning Effort Custom →
none(see the tools 400 below).Verified on this config:
gpt-5.6-terraexecutingrun_command/get_process_status, with the Files sidebar listing/home/userin the same conversationwrite_file/list_fileswriting to the sandbox (tested ongpt-3.5-turbobefore I re-enabled Terra), with files appearing immediately in the bind-mounted./sandboxon the hostTwo things that actually matter in that config
Use a path-safe
idI initially set
"id"to the URL. The Files sidebar then requests:The terminal ID is interpolated unencoded into the API path, so a URL-shaped ID puts
//and:inside the route and it can't match. (The query parameter is encoded —directory=%2F— just not the path segment.)Caveat on causation: I changed the id to
myterminaland wiped the database, so I can't say which was decisive — I never tested a clean database with a URL-shaped id. The 404 above is real and reproducible regardless, and the unencoded path segment looks like a bug either way.Worth noting the sidebar proxies through Open WebUI's own API rather than calling Open Terminal directly, which surprised me —
Sec-Fetch-Site: same-origin.The full JSON schema is required
A minimal
[{"url": ..., "key": ...}]was silently ignored — no error, no connection, nothing in the picker.access_grantsin particular seems to be required.I found the schema on the Terminals (orchestrator) page, not the Open Terminal connecting page where I'd been looking.
Diagnostic traps
Stale connection records survive UI deletion
After a few iterations I hit:
A database UUID — not any ID I'd configured. Deleting connections through the UI didn't clear it, and the frontend kept sending the old identifier. A HAR capture confirmed it was still requesting the previous URL-as-ID after I'd changed the config and recreated the container.
So I'd change something, test, see the same failure, and conclude the change was wrong — when it had simply never been used.
Everything worked immediately after.
If you've iterated on terminal connections at all, wipe before drawing conclusions.
Note the
sudo— the embedding cache, uploads andvector_db/chroma.sqlite3are root-owned even through a bind mount.Terminal errors are sticky per conversation
Once a chat shows
Terminal unavailable, it stays broken. Fixing the connection or switching selection doesn't recover it. Start a new conversation before retesting anything.Model settings behave the same way — change a param, test in the same chat, and you're silently testing the old config.
Between these two, a lot of my "that didn't work" conclusions were about stale state rather than the change I'd just made.
Things that looked necessary but weren't
I added all of these while chasing the problem, then removed them one at a time afterwards. None were needed:
/etc/hostsentry mappingopen-terminalto127.0.0.1, so one hostname would resolve from both host and container.Removing all three changed nothing. The Open Terminal log shows every request arriving from the open-webui container (
172.18.0.3), never from the host bridge (172.18.0.1). The docs' portless example is correct — everything goes over the Compose network.Separate finding: GPT-5.6 + tools → 400
Not related to the above, and I couldn't find it reported here:
LibreChat, LiteLLM, LangChain, Cline and RubyLLM have all filed this. Open WebUI is affected too.
Workaround: Admin → Models → edit model → Advanced Params → Reasoning Effort: Custom →
none→ Save & Update, then a new conversation. Confirmed working — Terra executes tools normally with this set.Cost: you lose GPT-5.6's reasoning. OpenAI's other suggested remedy is
/v1/responses— I couldn't find a way to select that in the UI.One smaller note: I'd disabled most of the ~116 OpenAI models to declutter the picker, and requests then fell through to
gpt-3.5-turborather than erroring. A database wipe resets these toggles, so check after any reset.Suggestions
URL-encode the terminal ID in the Files sidebar request path, or reject URL-shaped IDs at registration. Right now
TERMINAL_SERVER_CONNECTIONSaccepts an id ofhttp://open-terminal:8000without complaint, and the resulting request path is unparseable.Surface an error when
TERMINAL_SERVER_CONNECTIONSis malformed. An incomplete schema currently does nothing at all, which is hard to debug.Document the schema on the Open Terminal connecting page. It's only on the Terminals orchestrator page.
Make UI deletion actually clear the record, or expose what's registered. The mismatch between deleted-in-UI and still-in-database is what made everything else hard to diagnose.
Question
Where is the Responses API toggle? It's OpenAI's recommended fix for the GPT-5.6 tools error and would preserve reasoning, unlike
reasoning_effort: none. I couldn't find it in the UI or the env var reference.