[GH-ISSUE #33] ISSUE: Admin UI unreachable — "/" returns 401 "Missing Authorization header" because the frontend build is silently excluded from the wheel #11

Closed
opened 2026-10-18 21:13:21 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @Classic298 on GitHub (Jul 10, 2026).
Original GitHub issue: https://github.com/open-webui/terminals/issues/33

Bug description

Visiting the orchestrator root (http://host:3000/) in a browser to open the admin UI (added in 0.0.4) returns:

{"detail":"Missing Authorization header"}

instead of serving the UI. This happens with the official Docker image (ghcr.io/open-webui/terminals:0.0.5) and any image/install built with pip install ., whenever TERMINALS_API_KEY or TERMINALS_OPEN_WEBUI_URL is set.

Steps to reproduce

  1. Run the official image, e.g. docker run -p 3000:3000 -e TERMINALS_API_KEY=secret -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/open-webui/terminals:0.0.5
  2. Open http://localhost:3000/ in a browser.

Expected: the admin UI loads (TERMINALS_ENABLE_UI defaults to true).
Actual: 401 {"detail":"Missing Authorization header"}.

Root cause

The built frontend never makes it into the installed Python package:

  1. terminals/frontend/build/ is gitignored (root .gitignore has build/, and terminals/frontend/.gitignore has build).

  2. Hatchling honors .gitignore during wheel file selection and excludes VCS-ignored files even when they match an include pattern. The "terminals/frontend/build/**/*" entry under [tool.hatch.build.targets.wheel].include in pyproject.toml is therefore a no-op. Hatchling's documented override for shipping gitignored build outputs is the separate artifacts option, which is not used.

  3. So the wheel built by pip install . in the Dockerfile ships the frontend source (src/, package.json, …) but not frontend/build/. Verifiable in a running container:

    $ docker exec <container> pip show -f terminals | grep frontend
      terminals/frontend/.gitignore
      terminals/frontend/package.json
      terminals/frontend/src/routes/+page.svelte
      ...            # <-- no frontend/build/* entries
    
  4. At runtime the terminals console script imports the package from site-packages, where FRONTEND_BUILD_DIR.exists() (terminals/main.py) is false, so the GET / UI route is never registered.

  5. The browser request falls through to the auth-protected catch-all reverse proxy (/{path:path} in terminals/routers/proxy.py), whose verify_api_key dependency rejects it with 401 Missing Authorization header.

Debugging note: the image does contain the built UI at /app/terminals/frontend/build (the Dockerfile COPY --from=frontend target), which is misleading — that copy is dead weight; the app runs from site-packages.

Fix (verified)

Declare the build output as hatchling artifacts in pyproject.toml:

[tool.hatch.build.targets.wheel]
packages = ["terminals"]
include = [
    "terminals/**/*.py",
    "terminals/alembic.ini",
    "terminals/migrations/**/*",
    "terminals/frontend/build/**/*",
]
artifacts = [
    "terminals/frontend/build/**/*",
]

Rebuilding the wheel with this change makes terminals/frontend/build/** appear in the wheel, and the UI is then served on /.

Workaround for existing deployments

Until a fixed image is published, make Python prefer the complete source tree already inside the image, e.g. in docker-compose:

services:
  terminals:
    environment:
      PYTHONPATH: "/app"

or copy the build into site-packages:

docker exec <container> sh -c 'cp -r /app/terminals/frontend/build "$(pip show terminals | sed -n "s/^Location: //p")/terminals/frontend/"'
docker restart <container>

Affected versions

0.0.4 (UI introduced) and 0.0.5.

Originally created by @Classic298 on GitHub (Jul 10, 2026). Original GitHub issue: https://github.com/open-webui/terminals/issues/33 ## Bug description Visiting the orchestrator root (`http://host:3000/`) in a browser to open the admin UI (added in 0.0.4) returns: ```json {"detail":"Missing Authorization header"} ``` instead of serving the UI. This happens with the official Docker image (`ghcr.io/open-webui/terminals:0.0.5`) and any image/install built with `pip install .`, whenever `TERMINALS_API_KEY` or `TERMINALS_OPEN_WEBUI_URL` is set. ## Steps to reproduce 1. Run the official image, e.g. `docker run -p 3000:3000 -e TERMINALS_API_KEY=secret -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/open-webui/terminals:0.0.5` 2. Open `http://localhost:3000/` in a browser. **Expected:** the admin UI loads (`TERMINALS_ENABLE_UI` defaults to `true`). **Actual:** `401 {"detail":"Missing Authorization header"}`. ## Root cause The built frontend never makes it into the installed Python package: 1. `terminals/frontend/build/` is gitignored (root `.gitignore` has `build/`, and `terminals/frontend/.gitignore` has `build`). 2. Hatchling **honors `.gitignore` during wheel file selection and excludes VCS-ignored files even when they match an `include` pattern**. The `"terminals/frontend/build/**/*"` entry under `[tool.hatch.build.targets.wheel].include` in `pyproject.toml` is therefore a no-op. Hatchling's documented override for shipping gitignored build outputs is the separate `artifacts` option, which is not used. 3. So the wheel built by `pip install .` in the Dockerfile ships the frontend *source* (`src/`, `package.json`, …) but not `frontend/build/`. Verifiable in a running container: ``` $ docker exec <container> pip show -f terminals | grep frontend terminals/frontend/.gitignore terminals/frontend/package.json terminals/frontend/src/routes/+page.svelte ... # <-- no frontend/build/* entries ``` 4. At runtime the `terminals` console script imports the package from **site-packages**, where `FRONTEND_BUILD_DIR.exists()` (`terminals/main.py`) is false, so the `GET /` UI route is never registered. 5. The browser request falls through to the auth-protected catch-all reverse proxy (`/{path:path}` in `terminals/routers/proxy.py`), whose `verify_api_key` dependency rejects it with `401 Missing Authorization header`. Debugging note: the image *does* contain the built UI at `/app/terminals/frontend/build` (the Dockerfile `COPY --from=frontend` target), which is misleading — that copy is dead weight; the app runs from site-packages. ## Fix (verified) Declare the build output as hatchling `artifacts` in `pyproject.toml`: ```toml [tool.hatch.build.targets.wheel] packages = ["terminals"] include = [ "terminals/**/*.py", "terminals/alembic.ini", "terminals/migrations/**/*", "terminals/frontend/build/**/*", ] artifacts = [ "terminals/frontend/build/**/*", ] ``` Rebuilding the wheel with this change makes `terminals/frontend/build/**` appear in the wheel, and the UI is then served on `/`. ## Workaround for existing deployments Until a fixed image is published, make Python prefer the complete source tree already inside the image, e.g. in docker-compose: ```yaml services: terminals: environment: PYTHONPATH: "/app" ``` or copy the build into site-packages: ```bash docker exec <container> sh -c 'cp -r /app/terminals/frontend/build "$(pip show terminals | sed -n "s/^Location: //p")/terminals/frontend/"' docker restart <container> ``` ## Affected versions 0.0.4 (UI introduced) and 0.0.5.
Author
Owner

@Classic298 commented on GitHub (Jul 10, 2026):

https://github.com/open-webui/terminals/pull/34

<!-- gh-comment-id:4937745090 --> @Classic298 commented on GitHub (Jul 10, 2026): https://github.com/open-webui/terminals/pull/34
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
open-webui/terminals#11
No description provided.