Repository navigation
fix: remove dead Admin Panel entry, stop coverage suites littering the demo account (#846, #848) - #909
Conversation
The user menu's Admin Panel item navigated to /admin, which Caddyfile.owui already 404s outright: D-014 makes web-console and control-plane the sole admin surface and keeps Open WebUI's own admin panel off. The entry was the one thing in hive_ui_surfaces.py kept alive on purpose (asserted intact by a GUARDS entry), on the theory that Hive's tenant-owner promotion made it a real control. It never was: every tenant owner sees a top-level Admin Panel item that always 404s with no explanation, reconfirmed live in the 2026-08-11 demo-readiness walk (#858). Wiring the endpoint up instead would stand up a second admin surface next to web-console's, which is the exact duplication D-014 forecloses, so this removes the entry with the same unconditional !1 gate already used for the neighboring Playground entry, moves its pinned-bundle fixture from guards to rewrites, and updates the self-check that pins each rewrite's marker. /api/v1/terminals/ 404ing on every page load is not the same class of bug: Caddyfile.owui already documents it as a deliberate block (#770, an authenticated request-forgery primitive with no allowlist), and the front end already degrades cleanly to an empty terminal list. Left alone.
Every one of these suites signs in through the sanctioned magic-link
helper (docs/live-test-auth.md), so no password is ever read or
rotated, but the identity behind that session still matters: it is the
account that owns whatever chat, task or key the suite creates while
proving a control. Two currently-merged paths silently resolved that
identity to demo@hive-demo.invalid, the account the owner demos to
prospects.
- apps/web-console/tests/e2e/_probe/agent-workspace-flows.spec.ts
defaulted AGENT_EMAIL to demo@hive-demo.invalid whenever
HIVE_QA_AGENT_EMAIL was unset, "to keep the suite runnable by hand
with no setup." Every run past that default creates one real,
currently undeletable agent task (no per-task delete route exists
yet), which is why the account's task list is now a wall of
interaction-coverage and cancel-guard proof rows stuck Cancelled or
Blocked. Now fails hard in the same requireMintEnv check the suite
already uses for its Supabase mint variables, instead of defaulting.
- .github/workflows/chat-coverage.yml fell back to
secrets.HIVE_QA_TESTER_EMAIL whenever secrets.HIVE_QA_AGENT_EMAIL was
unset, which it always was: no such secret exists on this
repository, so every live sweep ran that fallback. The sweep's own
ledger records the result: "demo@hive-demo.invalid ->
hive-coverage-77811 survived a reload"
(docs/proof/chat-interaction-coverage-2026-08-10/coverage.run.json).
The workflow already has a loud "inputs the sweep cannot run
without" check for an empty HIVE_QA_AGENT_EMAIL; removing the silent
fallback is what lets that check actually catch this instead of
quietly succeeding against the wrong account.
docs/live-test-auth.md gets a new section stating the rule plainly for
the next suite: read-only live checks may use the demo account, same
as scripts/verify-control-plane.py; anything that sends a message,
submits a task, or mints a key must use a dedicated identity instead,
the same way the plain E2E suite already isolates itself with
E2E_RUN_KEY-scoped fixture accounts, or the way
seed-owui-e2e-user.py --account-slug stands up its own billing
account rather than sharing one.
Not fixed here, and flagged for the owner rather than silently
skipped: neither change provisions the dedicated identity itself
(HIVE_QA_AGENT_EMAIL has no value to fail over to), since that needs a
live account created with real Supabase admin credentials this
worktree does not hold. Both suites now fail loudly until that
identity exists and the secret is set, which is the intended
consequence: a workflow that used to succeed quietly against the wrong
account now requires the right one to be provisioned first.
The existing litter itself (chats, tasks, API keys, a spend alert on
the demo account) is not touched by this commit. It is enumerated for
the owner in the PR body; nothing is deleted without an explicit go.
Buglog entry (for the follow-up buglog-only PR against main, per
.claude/rules/openwolf.md):
{"date":"2026-08-16","tags":["owui","chat","cowork","test-litter","demo-account","ci"],"error_message":"Demo account (demo@hive-demo.invalid) accumulates permanent chat, agent-task and API-key rows from live coverage suites; user menu also offered a dead Admin Panel entry (404 on every path)","root_cause":"Two live-coverage code paths silently defaulted their sign-in identity to the shared demo account: agent-workspace-flows.spec.ts's AGENT_EMAIL fallback and chat-coverage.yml's HIVE_QA_AGENT_EMAIL-to-HIVE_QA_TESTER_EMAIL secret fallback (which resolved to the demo identity in practice, per its own coverage ledger). Separately, hive_ui_surfaces.py deliberately kept the OWUI Admin Panel user-menu entry alive via a GUARDS assertion, while Caddyfile.owui already 404s every path it points at (D-014: web-console/control-plane is the sole admin surface).","fix":"Removed the Admin Panel entry from hive_ui_surfaces.py (moved from GUARDS to REWRITES, same !1 gate as the neighboring Playground entry). Changed both silent demo-account fallbacks to hard failures (agent-workspace-flows.spec.ts's requireMintEnv, chat-coverage.yml's HIVE_QA_AGENT_EMAIL secret) so a live coverage run can no longer land on the demo account by omission. Existing litter enumerated for owner review, not deleted; a dedicated non-demo QA identity still needs to be provisioned and wired to HIVE_QA_AGENT_EMAIL as a follow-up.","tags":["owui","chat","cowork","test-litter","demo-account","ci"]}
|
Warning Review limit reached
Next review available in: 21 minutes Limit details: You’ve used all 1 included review currently available under your plan. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (10)
📒 Files selected for processing (10)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Adversarial review (stage 6), pipeline mode, single pass since the builder had no agent-dispatch tool. Verified against the pushed diff and the surrounding untouched files, not just the PR body's claims. Fail-loud change (#848), risk of a permanently-red check nobody looks at: does not apply here. Admin Panel removal, cosmetic vs real: real, and correctly scoped. Third silent-default identity path: found one, posted inline on Pinned-bundle fixture drift: verified robust. Terminals claim (#770): verified true and pre-existing, untouched by this PR. Review Summary
Verdict: WARNING. One HIGH issue ( |
Stage 6 review on PR #909 found a third silent-default identity path this PR had missed: scripts/verify-control-plane.py:49 defaulted HIVE_VERIFY_EMAIL to demo@hive-demo.invalid, the same ??-style fallthrough already fixed in the other two paths. Unlike those two, this script does not just sign in: its api-key lifecycle check mints a real API key and sends a real POST /v1/chat/completions through it, a real write and a real spend against whatever tenant the caller belongs to. The mint call was correctly attributed as a HIGH finding. Fixed the same way as the other two paths: HIVE_VERIFY_EMAIL now has no default and main() exits loudly, before any HTTP call, when it is unset. Confirmed no other caller depends on the old default: the script is invoked from no CI workflow and imported by no other module (the hyphenated filename cannot be imported as a regular module, confirmed by grep across .yml/.md/.py for both the script name and the two env vars it reads). docs/live-test-auth.md's new section from the previous commit had cited this script as a compliant read-only example, which was itself wrong given what the script actually does; corrected both the new section and the pre-existing paragraph about it to stop endorsing that reading. Also fixes the LOW finding from the same review: apply_ui_surfaces_patch.py's docstring still described the Admin Panel entry as a kept, guarded surface after the previous commit moved it to REWRITES. Updated to name the two guards that remain (Knowledge, Settings dialog) and to record why Admin Panel is no longer among them. Added scripts/test_verify_control_plane.py: a minimal self-check (no framework, no network) asserting the default is empty and that main() exits before any HTTP call when HIVE_VERIFY_EMAIL is unset, mirroring the importlib pattern already used by scripts/test_seed_demo_owner.py for a hyphenated script filename. All four self-checks re-run clean: test_owui_ui_surfaces.py, test_caddy_owui_blocklist.py, test_owui_rag_env_config.py (unchanged, sanity check), test_verify_control_plane.py (new).
|
Both threads addressed in e070537. HIGH (verify-control-plane.py). Confirmed and fixed the same way as the LOW (apply_ui_surfaces_patch.py docstring). Fixed: now names the two All four self-checks re-run clean after the fix. |
Visual proof, with an explicit split between what is deployed and what is notCaptured 2026-08-16. Every artifact is committed in This branch is not deployed. The demo box runs 1. The #846 claim: the entry leaves the client bundleBoth images were built from the same pinned upstream digest and the same Built from Built from this branch: The menus read out of the DOM, so the difference does not depend on reading Nothing else in the menu moved. What this does not prove. It proves the menu, not the route. 2. The deployed menu today, shown rather than omitted
3. The #848 account state, after the enumerated litter was removedThese are live captures. They show the account, not the branch's code. Chat sidebar, API keys, with the six Spend alerts, after removing the one addressed to Cowork task rows were deliberately left alone in this pass: another change is Provenance and redactionSessions came from |
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Two Dockerfile.open-webui images (main vs this branch), same pinned digest, differing only in owui-patches/, run standalone against a stub gateway serving the real six-alias catalog shape. Confirms the fix still applies after the Phase 20 catalog/routing changes that landed on main since this PR opened: main lists all six aliases in the chat picker, this branch lists only the three chat ones. Same method PR #909 used for its user-menu proof.
…eads (#792) (#814) Fixes #792. Supersedes the mechanism in #776, which is left in place but is not what fixes this. ## The diagnosis, confirmed against the pinned image #776 hid `hive-embedding-default`, `hive-stt` and `hive-tts` by writing Open WebUI's per-model `access_control`. It was reviewed, tested, merged and deployed, and it changed nothing. Two independent reasons, both read out of `ghcr.io/open-webui/open-webui:v0.10.2@sha256:9fcea9c…` rather than assumed: 1. `deploy/docker/docker-compose.yml:738` sets `BYPASS_MODEL_ACCESS_CONTROL: "true"`. `env.py:749` turns that into a module constant, and the picker's listing path, `main.py:842` in the `@app.get('/api/models')` handler, calls `get_filtered_models`, which begins `if (user.role == 'user' or ...) and not BYPASS_MODEL_ACCESS_CONTROL:` and otherwise `return models` unchanged (`utils/models.py:418-472`). The flag switches the filter off for every role, so the `access_control` values #776 writes are never read. 2. Even with that flag off, `get_filtered_models` exempts administrators whenever `BYPASS_ADMIN_ACCESS_CONTROL` is set, and that defaults to true (`config.py:2029`), while the `#457` tenant-role patch promotes every tenant owner to an Open WebUI administrator. Every human on the demo box is an admin. So the mechanism could not fire on either count. Note that every test shipped with #776 still passed, because they all asserted that the correct values were **written** and none asserted that anything was **read**. ## What actually depends on the bypass, measured The tempting fix is to drop the flag. #792 flags document RAG and text-to-speech as the risk. That turned out to be wrong in both directions, and the measurement is what decided this PR's design. An isolated harness (the pinned image, a pgvector container, and a stub gateway serving exactly the six catalog aliases and answering chat, embeddings, `audio/speech` and `audio/transcriptions`), with Open WebUI's environment mirroring the `open-webui` service block, run against two personas: an Open WebUI `admin` (what a tenant owner actually is) and an Open WebUI `user`. | | admin persona | member persona | | --- | --- | --- | | Bypass on (as deployed) | picker lists all 6, chat 200 | picker lists all 6, chat 200 | | Bypass removed | picker **still lists all 6**, chat 200 | picker **empty**, chat **400 "Model not found"** | Document RAG upload, ingest and query, TTS and STT all returned 200 for both personas in both configurations. None of them consults Open WebUI's model registry: `routers/audio.py` contains no access-control call at all, and the RAG embedder issues a direct HTTP request to `RAG_OPENAI_API_BASE_URL`. So the bypass does not protect RAG or TTS. It protects chat for non-admin members, and removing it would not have hidden a single alias from the people who use the demo. Full runs in `docs/proof/issue-792/probe-*.json`. ## The fix Filter the listing the picker reads, and nothing else. A build-time splice into `main.py`'s `/api/models` handler, the same posture as this Dockerfile's other Open WebUI patches. It runs on the response: `request.app.state.MODELS` is untouched, so chat, RAG embeddings and text-to-speech still resolve every alias. `GET /v1/models` on the API origin is not touched at all, there are no Go changes in this PR, and that endpoint stays an OpenAI-contract surface that `support-matrix.json` marks supported and that direct API clients depend on. Two things are deliberate because of how #776 failed: - The inserted call is **unconditional**, and `assert_unconditional` in the patch fails the build if a future edit puts it behind a flag, a role check or an access-control branch. There is no conditional available here that this deployment does not already disable. - The transform lives in a `patch()` function rather than inline, so the guard can run the real thing against a checked-in verbatim excerpt of the pinned image's own handler (`pinned-main-excerpts.json`). PR CI never builds this image, which is exactly why that excerpt is committed, following the precedent `dump_bundle_excerpts.py` already sets. The hidden set is never hardcoded. It is `HIVE_PICKER_HIDDEN_MODEL_IDS` (compose owns it) unioned with whatever `RAG_EMBEDDING_MODEL`, `AUDIO_TTS_MODEL` and `AUDIO_STT_MODEL` name, so changing the admin-selected embedding alias does not need a second edit (D-001). Unset variables contribute nothing, so a deployment that sets none of them keeps upstream behaviour. ### Rejected - **Dropping `BYPASS_MODEL_ACCESS_CONTROL`.** Measured above: breaks chat for every member, hides nothing from admins. - **Anything access-control shaped**, including repairing #776. Inert by construction on this deployment. - **Filtering `/v1/models` in edge-api by shim identity.** `owui_unwrap.go` keeps `hasShimAuthorization` unexported precisely so no route branches on "is this the shim key", and `handleModels` documents that there is no exception for it. Both would have had to be reversed. - **Filtering `/v1/models` for everyone.** Violates the OpenAI contract; upstream OpenAI lists embedding and audio ids too. ## The guard `scripts/test_owui_model_picker_filter.py`, wired into `make test-scripts`, which the required `repo-policy-lints` check runs on every non-docs change. It is built to fail the way #776 would have failed. It asserts the filter reaches the image and lands on the response path between upstream's own filtering and the return, rather than asserting a value was written, and it asserts the coupling directly: while compose sets the bypass, a picker filter that does not depend on access control must be present and must run in the image. **Red, against the pre-fix image with the bypass on, which is the deployed world:** ``` OWUI chat model picker filter regression (1 failure(s)): run_live: the chat picker still lists ['hive-embedding-default', 'hive-stt', 'hive-tts'] (issue #792). The picker filter is not on the response path, or something is gating it. exit=1 ``` **Green, patched image, bypass still on, same harness, same command:** ``` live: gateway serves 6 models including all 3 non-chat aliases; picker lists 3 and none of them OWUI chat model picker filter: 10 checks passed exit=0 ``` The static half that CI runs goes red the same way with the Dockerfile's `RUN` line neutered and compose untouched, naming both the missing invocation and the bypass coupling. Transcripts in `docs/proof/issue-792/README.md`. ## Live proof Both halves, captured against a running stack, in one signed-in session. Picker before, signed in as the owner persona. All six, including the three that cannot serve a completion:  Picker after, same persona, same harness, patched image. Three chat aliases:  The gateway's own list in that same session, post-fix, still carrying all six:  And `/api/models`, the listing the picker reads, post-fix:  Probe C confirms the rest of the surface is unaffected with the fix in place and the bypass left on: chat 200, RAG ingest completed, RAG query returning a hit, TTS 200 and STT 200, for both personas. ## Test plan - [x] `make test-scripts` green, including the new guard - [x] New guard proved red against the pre-fix image and green against the patched one, same harness - [x] New guard proved red statically with the Dockerfile `RUN` line neutered - [x] Image builds; every patch assertion in `Dockerfile.open-webui` passes - [x] Picker shows only the three chat aliases, admin and member personas - [x] Gateway model list still returns all six in the same session - [x] Chat, document RAG ingest and query, TTS and STT all still work post-fix - [ ] Confirmed on the demo box after deploy, which this PR does not perform ## Notes for the reviewer The Open WebUI `AUDIO_*` settings are wired in the harness even though `docker-compose.yml` does not set them today. #792 named text-to-speech as a risk, and a risk that is not configured cannot be measured. That harness wiring is not part of this diff. #776's control-plane machinery is left untouched. It is inert on this deployment but harmless, and it becomes live if the bypass is ever turned off, which is a decision for whoever makes it. The guard makes that coupling explicit rather than silent. ## Rebased onto main, 2026-08-17 This PR sat open since 2026-08-09. A lot landed on `main` in that window, including Phase 20 provider-catalog waves and pricing corrections, so this branch was merged forward (`4e39de40`) and re-verified against current code rather than assumed still correct. Checked, not assumed: - The pinned image digest is unchanged (`ghcr.io/open-webui/open-webui:v0.10.2@sha256:9fcea9c…`). - `public.model_aliases` has not grown a new non-chat alias and none of the three (`hive-embedding-default`, `hive-stt`, `hive-tts`) were renamed; confirmed against every migration that touches the table, most recently `20260801_13_alias_price_unit.sql`. - `scripts/test_owui_model_picker_filter.py` still passes statically (10/10) against the checked-in `pinned-main-excerpts.json`. - `docker-compose.yml`'s `HIVE_PICKER_HIDDEN_MODEL_IDS` default and `RAG_EMBEDDING_MODEL`/`OWUI_RAG_EMBEDDING_ALIAS` wiring are unchanged and still name the same three aliases. One cleanup from the merge itself: `.wolf/buglog.jsonl`'s `merge=union` driver kept this branch's own append after merging `main` forward. Branch appends to that file are never allowed regardless of the merge driver (`.claude/rules/openwolf.md`); dropped it back to `main`'s content in a separate commit and moved the entry to **Buglog entry** below. ### Fresh A/B proof, same method as PR #909 The demo box still runs `main`, so nothing captured against it can show this fix. Two `Dockerfile.open-webui` images, same pinned digest, differing only in `owui-patches/`, run standalone against a stub gateway serving the real six-alias catalog shape. Full method and logs: `docs/proof/pr-814-rebase-verification-2026-08-17/README.md`. Built from `main` (no patch) — all six aliases in the dropdown:  Built from this branch — three chat aliases:  DOM read backing each screenshot (which of the six known ids actually rendered in the opened dropdown), not just the image: ``` main: ["hive-auto","hive-default","hive-fast","hive-embedding-default","hive-stt","hive-tts"] branch: ["hive-auto","hive-default","hive-fast"] ``` ## Is filtering the picker the right fix, or should these aliases not be in the chat-facing catalog at all? Asked directly, and the honest answer has two parts, because a design pass run in parallel on this same question reached a real finding that this PR's own "Rejected" section above already anticipated and rejected once. **The finding, verified against current code and correct as stated:** `public.model_aliases` carries no `modality`/`is_chat_model` column, `apps/control-plane/internal/catalog/repository.go`'s alias-listing queries (`ListPublicAliases`, the tenant-visibility query, `GetAlias`, `ListAllAliases`) never join `provider_capabilities`, and `apps/edge-api/cmd/server/main.go`'s `handleModels` (~line 774) serializes `snapshot.Models` verbatim. So yes: `GET /v1/models` on the API origin lists `hive-embedding-default`, `hive-stt` and `hive-tts` with nothing marking them as non-chat, for every caller, chat-scoped or not. **Where the conclusion drawn from that finding doesn't hold:** the proposal was to filter `/v1/models` itself so it "returns only chat-capable models for a chat-scoped request." This PR's own Rejected section already considered and rejected the unscoped version of that ("Filtering `/v1/models` for everyone. Violates the OpenAI contract; upstream OpenAI lists embedding and audio ids too.") for a reason that's still true: a real OpenAI `/v1/models` response is undifferentiated too, and a direct API client that wants to call `POST /v1/embeddings` or `POST /v1/audio/speech` with a Hive alias needs to discover it via `GET /v1/models` first, the same way it would against upstream OpenAI. Stripping the three aliases from the endpoint unconditionally would break that discovery path for every non-chat SDK integrator to fix a problem that, for them, does not exist: nobody calling `/v1/embeddings` is confused by seeing `hive-embedding-default` in the model list. **So: keep #814's picker filter. Do not expand this PR to touch `/v1/models`.** The client-side fix stays the answer to the owner's actual complaint (Open WebUI's chat picker specifically), and it is not "defense in depth" for a server fix that doesn't exist — until something reconfigures Open WebUI to consume a genuinely chat-scoped listing, this patch is the only thing making the picker correct. There is a real, narrower question worth designing separately: a **chat-scoped** variant of the listing, for a caller that opts in to "only models I can send a chat completion to" (which Open WebUI's picker could then consume server-side instead of client-side). That needs its own signaling mechanism (query param, header, or a distinct endpoint) so the unparameterized `GET /v1/models` keeps its current, contract-correct shape. No migration is needed to build it either way: the capability data already exists twice over and is unused by the listing endpoint today — `apps/control-plane/internal/catalog/http.go`'s tested `isNonChatModality` over `capability_badges` (currently wired only to the inert, bypassed #776 OWUI `access_control` sync), and `provider_capabilities.supports_chat_completions` / `supports_embeddings` / `supports_tts` / `supports_stt`, already joined per-route in `listRouteSnapshots` for routing decisions but never surfaced to `handleModels`. Filed as #931 rather than folded into this PR, since it's a genuine design question (is there real demand for it beyond this one picker?) and, if built, its own reviewed diff. ## Buglog entry Carried here per branch policy (never appended to `.wolf/buglog.jsonl` on a feature branch). To be appended to `main` in a separate buglog-only PR once this merges: ```json {"id":"bug-msmbcjgc-8012bd","timestamp":"2026-08-09T21:27:12.251Z","related_bugs":[],"occurrences":1,"last_seen":"2026-08-09T21:27:12.251Z","error_message":"Open WebUI chat model picker still listed hive-embedding-default, hive-stt and hive-tts after PR #776 merged and deployed (issue #792)","root_cause":"#776 hid them by writing Open WebUI per-model access_control, but deploy/docker/docker-compose.yml sets BYPASS_MODEL_ACCESS_CONTROL true, which makes main.py skip get_filtered_models for every role in the pinned v0.10.2 image. Even with that flag off, get_filtered_models exempts admins whenever BYPASS_ADMIN_ACCESS_CONTROL is set, and it defaults to true while this deployment promotes every tenant owner to an Open WebUI admin. The mechanism could never fire, and every test shipped with it asserted that values were written rather than read.","fix":"filter the /api/models response instead, via a build-time patch (deploy/docker/owui-patches/apply_model_picker_patch.py plus hive_model_picker.py). Unconditional, env-driven, applied after upstream own filtering and before the return, leaving request.app.state.MODELS untouched so chat, RAG embeddings and TTS still resolve every alias. Measured on a booted container: removing the bypass instead gives members an empty picker and HTTP 400 Model not found on chat, while leaving RAG and TTS unaffected, so the bypass protects chat and not the named risks. Guard: scripts/test_owui_model_picker_filter.py, with a --live mode that fails when the picker still lists the aliases.","tags":["open-webui","model-picker","access-control","inert-fix","issue-792","issue-776","docker-compose"]} ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…ust from one compose file (#936) Owner instruction, 2026-08-17: the chat interface is "overly future-proofed with unnecessary features" and the unused elements must go. This is the subtraction half only. The information architecture and visual redesign belong to the separate agent already working on them, so nothing here designs replacement navigation. ## What was actually wrong The three surfaces the owner named (Notes, Calendar, Automations) were already off on the demo box, and only there. `docker-compose.yml` names three environment variables and `owui-patches/hive_rag_env_config.py` reconciles them onto that deployment's database. That is a complete control for one deployment and no control at all anywhere else: upstream's own defaults are all on, so a plain `docker run` of the Hive image rendered every one of them in full. The `before` capture in this pull request produces the owner's list word for word, which is what identifies the image, rather than the deployment, as what he was looking at. Off has to be a property of the product, not of one compose file that a bare run, an enterprise override or the next proof harness can fail to repeat. The second half of the question was whether hiding a menu entry is honest in each case. For three of the four flag-backed surfaces it is: `calendar.py` checks its flag on all 13 of its routes, `automations.py` on all 8, `memories.py` on all 11. `notes.py` checks it on none of its 9, so with the entry gone and the page route already 404'd, `POST /api/v1/notes/create` still created a note for any signed-in user. That one gets a proxy rule; the other three deliberately do not, because a second control there would be decoration. ## Changes | file | change | | --- | --- | | `Dockerfile.open-webui` | `ENV ENABLE_NOTES/ENABLE_CALENDAR/ENABLE_AUTOMATIONS/ENABLE_MEMORIES/ENABLE_VERSION_UPDATE_CHECK=false`, so the image itself ships the reduced product. Docker `ENV` rather than a rewrite of upstream's own `os.getenv` lines: no literal to drift on a digest bump, and compose still overrides it, so an enterprise deployment that wants Notes back sets the variable to true and the reconcile flips the persisted row | | `docker-compose.yml` | adds `ENABLE_MEMORIES: "false"`. The compose values stay, all of them: on a deployment that has already booted they are still the only thing that reaches the database | | `hive_rag_env_config.py` | adds `memories.enable` to the reconciled set | | `hive_ui_surfaces.py` | two new bundle rewrites, `settings-admin-link` and `settings-vendor-translate-link` | | `Caddyfile.owui` | `api/v*/notes` added to `@blocked` | | `scripts/test_owui_ui_surfaces.py`, `scripts/test_caddy_owui_blocklist.py` | assertions for all of the above, written before the changes | Settings > Personalization is the only tab removed. Its feature is upstream's own Memory: nobody chose it, `ENABLE_MEMORIES` defaults to true, and while it is on `Chat.svelte` adds `features.memory: true` to every turn, so each message also pays for a lookup against a second memory store that Open WebUI owns and Hive does not read. Hive's memory subsystem is one subsystem on Hive's own Postgres (`.wolf/decisions.md` D-020) and is not built yet, so this is a competing store rather than a head start on it. The two new bundle rewrites were found by looking at the screen this change was already photographing, not by re-reading an issue. The Settings dialog's bottom-left "Admin Settings" link is the twin of the user menu's Admin Panel entry removed in #909 and points at the same `/admin` route Caddy already 404s, so every tenant owner sees a permanent control that always fails. The language picker still carried "Help us translate Open WebUI!", linking a Hive customer to the vendor's contributing guide by name, which is the same family as the Documentation and Releases links removed in #772. ## Two different promises, said plainly Removing a surface from one compose file while the feature still ships in the image is a half-removal that reads as complete and is not. This pull request makes the promise explicit per surface, because they are not the same promise. **Gone from the built image.** Cannot be brought back by configuration on any deployment, because the entry no longer exists in the client bundle. Workspace Models, Prompts, Tools and Skills; the Playground in both the sidebar and the user menu; the user menu's Admin Panel; the vendor's Documentation and Releases links; the release-notes dialog; the vendor branding on Settings > About; the Settings > Integrations tab; and now the Settings dialog's "Admin Settings" link and its "Help us translate Open WebUI!" link. **Off in the image, end to end, and re-enablable on purpose.** Notes, Calendar, Automations, and the Memory feature behind Settings > Personalization. The image now ships all four false, so the reduced product is what any run gets, and the navigation entry, the page and the API go together because each router refuses on the same flag. A deployment that genuinely wants one back sets the variable to true and the reconcile flips the persisted row, which is why these stay flags rather than becoming bundle rewrites. Before this change all four were on in the image and off only where `docker-compose.yml` was present, which is exactly the half-removal the title names. **One residual, stated rather than papered over.** `/api/v1/notes` is closed at the proxy, not in the image. `notes.py` is the one router in that set that checks its flag on none of its routes, so with `notes.enable` false its 9 endpoints still answer and `Caddyfile.owui` is what refuses them. Every deployment this repository ships puts that Caddy in front of Open WebUI, so there is no shipped configuration where those endpoints are reachable, but a bare `docker run` of the image is not covered. Closing it in the image would mean unmounting an upstream router in `main.py`, which would also make the feature un-re-enablable by configuration and so break the promise directly above. Deleting the upstream routers and Svelte pages outright is a fork-and-rebuild move that belongs to the redesign, not to a subtraction pass. ## Enumeration Taken from the pinned image itself, `ghcr.io/open-webui/open-webui:v0.10.2@sha256:9fcea9c...`, not from the deployment and not from memory. Worth recording separately: that image ships JavaScript source maps whose `sourcesContent` carries the complete original Svelte source, 612 files. The repeated claim in `owui-patches/` that the image ships "no frontend source" is true only of the build tooling. Reading a gate no longer requires reverse-engineering minified identifiers. ### Sidebar | entry | verdict | why | | --- | --- | --- | | New Chat | keep | the product | | Search | keep | chat history search, used | | Notes | remove | upstream note-taking, no Hive counterpart, no Hive code reads it | | Workspace | keep | its only remaining tab is Knowledge, which is the RAG document upload | | Folders | keep | chat organisation | | Chats, pinned chats, archived | keep | the product | | Channels | already off | upstream default is off; group chat, not a Hive feature | | pinned Models section | keep | renders only when a user pins a model | ### User menu | entry | verdict | why | | --- | --- | --- | | Settings | keep | reduced, see below | | Archived Chats | keep | real, reachable, used | | Workspace | keep | as above | | Notes, Calendar, Automations | remove | as above | | Sign Out | keep | the product | | Admin Panel, Playground, Documentation, Releases, Keyboard Shortcuts help block | already removed | #772, #909 | | Update your status, and the profile header it is wrapped in | design question, see below | | Active Users count | design question, see below | ### Settings dialog Nine tabs upstream, eight after #771 removed Integrations, six after this change. | tab | verdict | why | | --- | --- | --- | | General | keep | theme, language, per-user system prompt, advanced parameters. Its vendor translation link is removed here | | Interface | keep for now | see the design question below. Roughly forty upstream toggles, which is the "confusing" the owner named, but reducing it is composition, not subtraction | | Personalization | remove | upstream Memory, nobody chose it, competes with D-020 | | Audio | keep | speech-to-text and text-to-speech, a locked demo capability | | Data Controls | keep | chat import, export, archive-all, delete-all | | Account | keep, with a flag raised below | name and profile image are real. Its API keys, JWT token, notification webhook and password-change blocks are already hidden by upstream defaults this deployment does not change, correctly, since the Hive console owns keys and every account is SSO-only | | About | keep | already de-branded by #784 | | Connections | already off | `ENABLE_DIRECT_CONNECTIONS` defaults off; direct browser-to-provider connections would bypass the gateway | | Integrations | already removed | #771 | ## Deferred as design questions Each of these is a removal whose only available mechanism also takes something else, or whose answer is where the feature moves rather than whether it dies. Left alone deliberately, for the redesign owner. 1. **Update your status, and the user menu's profile header.** The presence feature is unambiguously unused. Its only flag, `ENABLE_USER_STATUS`, gates the whole `{#if profile}` block, so turning it off also removes the avatar, name and email from the top of the menu. Removing the status controls alone needs a bundle rewrite, and whether the identity header belongs in that menu at all is a navigation decision. 2. **Active Users count.** Its flag does not reach this deployment's audience: the gate is `enable_public_active_users_count || role === 'admin'`, and every tenant owner is an Open WebUI admin here, so hiding it needs a bundle rewrite. Small, but it is a decision about what the menu is for. 3. **Settings > Interface.** Around forty upstream toggles, most of which no Hive user will ever want. Reducing it is the settings rebuild, explicitly out of scope for this pull request. 4. **Settings > Account bio, gender and birth date.** Ungated personal-data collection with no consumer anywhere in Hive: nothing reads these fields. Flagged rather than removed because it sits inside a tab the redesign is about to rework, and because deleting input fields for data some accounts may already hold is a data question, not a navigation one. 5. **Per-message rating.** `ENABLE_MESSAGE_RATING` defaults on, so every assistant message carries thumbs up and down, and the feedback rows they write are only ever surfaced in Open WebUI's own admin Evaluations page, which is blocked. The data goes nowhere. Not reachable from the sidebar, the user menu or Settings, so it is outside this pull request's enumeration. 6. **Code interpreter and code execution.** Both default on and put a toggle in the chat input. Whether they overlap Cowork and the coding agent, or complement them, is a product call. 7. **Follow-up, tag and title auto-generation.** All default on and each spends a real model call per turn. A cost decision, not a surface one. ## Files touched, for collision checking The navigation-shell work should not overlap any of these. Eight files outside `docs/proof/`: ``` deploy/docker/Caddyfile.owui deploy/docker/Dockerfile.open-webui deploy/docker/docker-compose.yml deploy/docker/owui-patches/hive_rag_env_config.py deploy/docker/owui-patches/hive_ui_surfaces.py deploy/docker/owui-patches/pinned-bundle-excerpts.json scripts/test_caddy_owui_blocklist.py scripts/test_owui_ui_surfaces.py ``` `hive_ui_surfaces.py` and its `pinned-bundle-excerpts.json` fixture are the one real collision risk, since any other change that removes a surface by bundle rewrite lands in the same table and regenerates the same fixture. Nothing else here is frontend code at all. ## Test plan - [x] `make test-scripts`, which is what CI runs for these files. New assertions were written first and observed failing: image ENV absent, `ENABLE_MEMORIES` absent from compose and from the reconcile, `/api/v1/notes` not blocked, memories missing from the coverage table. - [x] Image built from this branch, the bundle rewrite pass reporting all 22 surfaces matched and both guards intact. - [x] Before and after images built from the same pinned digest, menus and settings tabs read back out of the DOM. - [x] Notes API probed with a real session token, direct and through a real Caddy on both configurations. - [x] Whole interface run through the branch's own Caddy, recording every console error and every 4xx or 5xx: one pre-existing 404 on `/api/v1/terminals/` (#770) and no request to `/api/v1/notes` at all. - [x] `npm run lint:proof-tokens`. - Not run: the six locked demo capabilities are untouched by this diff. Chat, embeddings, voice, knowledge, Cowork and the coding agent have no path through Notes, Calendar, Automations, Memory, the Settings admin link or the vendor translation link, and the kept APIs were probed alongside the blocked one. ## Visual proof `docs/proof/owui-surface-subtraction-2026-08-17/`, per the visual-proof rule. Attached below as well. ## Buglog entry ```json {"id":"owui-surfaces-image-vs-compose-2026-08-17","date":"2026-08-17","title":"Removed Open WebUI surfaces were off in one compose file and on in the image","error_message":"Notes, Calendar and Automations rendered in the user menu and sidebar, and Settings > Personalization in the settings dialog, on any run of the Hive Open WebUI image that did not repeat docker-compose.yml's ENABLE_* lines, including the repo's own before/after proof builds. Separately, POST /api/v1/notes/create returned 200 and created a note with notes.enable false.","root_cause":"Two independent gaps with the same shape. First, the removal of a product surface was expressed only as environment variables in docker-compose.yml plus a persisted-config reconcile, so it reached exactly one deployment and nothing else; upstream's defaults for all of them are on, and PR CI never builds Dockerfile.open-webui, so image and compose could disagree indefinitely with no signal. Second, a feature flag was assumed to be a complete control without checking that the backend honours it: calendar.py, automations.py and memories.py check theirs on every route, notes.py checks its own on none of its 9, so the flag hid the navigation entry and left the API fully callable.","fix":"Dockerfile.open-webui sets ENABLE_NOTES, ENABLE_CALENDAR, ENABLE_AUTOMATIONS, ENABLE_MEMORIES and ENABLE_VERSION_UPDATE_CHECK false as image ENV defaults, so the reduced product is a property of the image while compose keeps overriding it and the reconcile keeps reaching already-booted databases. Caddyfile.owui adds api/v*/notes to @Blocked. scripts/test_owui_ui_surfaces.py gains _image_env() and asserts the image, the compose value and the reconcile entry together for every flag-backed surface, plus that a non-persisted flag is deliberately absent from the reconcile.","tags":["open-webui","docker","feature-flags","caddy","persisted-config","dead-code","ui"]} ``` Closes nothing on its own; the redesign issue tracks the rest.
…ne Hive navigation (#938) Wave one of `spec-2026-08-16-hive-ui-redesign` (Obsidian vault), which is the fork plus the navigation shell. It answers the three things the owner named: the left navigation carries neither the agent workspace nor the chat interface, the product still looks like Open WebUI, and the agent is reached through a button in the top right corner that opens a separate page. All three were properties of shipping a prebuilt vendor bundle, so this stops doing that. ## What lands **The fork.** `vendor/open-webui` is upstream at `v0.10.2`, added as a squashed git subtree. `Dockerfile.open-webui` gains a first stage that compiles its frontend the way upstream's own Dockerfile does (`node:22-alpine3.20`, `npm ci --force`, `npm run build`) and copies the result over `/app/build` in the pinned image. The Python backend, its dependency set and every existing backend patch are untouched, so a digest bump keeps delivering backend fixes for free. The stage asserts the vendored `package.json` version equals the version inside the pinned image, so a bump without a matching subtree pull fails the build rather than a browser. Measured, cold, no cache: `npm ci` about 90 seconds, `npm run build` about 420 seconds. Warm, with the vendored tree untouched, both layers are cache hits. `owui-nightly.yml` boots the stack with `--build`, so its timeout moves from 30 to 45 minutes; `docker-bake.hcl` gains an `open-webui` target so the image is one command anywhere. **The identity.** `packages/hive-tokens/tokens.css` carries the design system (`spec-2026-08-11-hive-design-system`) as plain CSS custom properties, outside the vendored tree on purpose so it survives a change of chat engine. Open WebUI funnels its entire surface and ink scale through fourteen values in one `@theme` block; remapping those to the brand's warm ramp is what turns the product from grey to cream and charcoal in one place rather than a hundred. Lightness is held within a step or two of upstream's own ramp, so no contrast pair inverts. Hanken Grotesk and Geist Mono are served from the deployment, not from a font CDN. **The shell.** `vendor/open-webui/src/lib/hive/` holds every Hive authored piece: the navigation as data, a row component, and the stylesheet. Chats, Agents and Knowledge are labelled destinations, present in both sidebar states, with the current row carrying a coral bar as well as `aria-current`. Upstream's own files carry four insertion points a rebase can replay, listed in `docs/owui-fork.md`. **The agent inside the shell.** `/agents` is a route in the chat application that renders the agent workspace over the same origin, so reaching the agent no longer leaves the product. The injected launcher overlay, `loader.js`, and the measured `right: 120px` apparatus in `custom.css` are deleted with it. **Surfaces removed in source rather than in a bundle.** The Workspace tabs with no Hive counterpart and their routes, the Playground, the Admin Panel entry, the vendor's documentation, releases, changelog dialogue, social badges and copyright, the Settings Integrations tab, and the vendor links on the backend-required error screen. The accessible names the patch layer used to add are attributes in source now. ## Visual proof `docs/proof/shell-nav-2026-08-17/` and the comment below. Two images built from the same pinned digest, differing only in this diff, captured in both themes with the DOM read as well as the pixels, the way PR #909 did it. Reproducible with the `run.sh` and `capture.mjs` committed beside the captures. Checked against the design system's own floors, from `dom.json` rather than by eye: nav row height 32 matching the token, 44 under a coarse pointer, the two ring focus treatment (2px canvas then 2px coral) so the ring stays visible on the accent itself, transition 120ms which is `--hv-duration-fast`, and 0s under `prefers-reduced-motion`. ## What is deliberately not here **The bundle rewrite layer is not retired.** It still runs, against the bundle it was written for, and the frontend built from source then replaces that bundle. That reads as waste and it is deliberate for one release. Those rewrites are exact literals over minified output and the identifiers in them are allocated per chunk, so any edit to our own source renames them: the first build of this stage failed on `sidebar-playground-item`, `changelog-modal` and `about-vendor-social-badges`, three surfaces that had not actually come back, and an identifier-tolerant pattern for two of them then matched sibling components that must not be touched. Every surface those rewrites remove is removed in source here, so nothing regresses; what they still buy is the digest-drift guard. Retiring them properly is the natural next change and it is out of this one on purpose, because #936 was in flight against `hive_ui_surfaces.py` and two edits to that file from different directions is how a surface silently returns. **`hive_ui_surfaces.py`, `apply_ui_surfaces_patch.py` and `remove_integrations_tab.py` are byte identical to `main` in this branch.** **Wave two and beyond.** The composer mode control, the greeting, the suggestion chips, the settings reduction from nine sections to five, the console group in the same sidebar, Artifacts as a nav row (it needs its host origin injected into the chat frontend, which is a compose change), and everything in section 4 of the spec: the agent as a conversation, activity rows, approval, steering. The agent panel is framed rather than ported, because a native port needs a token bridge: this frontend holds Open WebUI's own session token while `/v1/agent/*` authenticates the Supabase bearer that only the embedded application carries. ## What must not regress | Capability | Status | |---|---| | Chat | The transcript, composer and model picker are upstream's, unchanged in behaviour. The nightly Open WebUI e2e suite is the gate; label this PR `run-owui-e2e` to run it | | Embeddings | Untouched. No API surface changes here | | Voice to text | Untouched. The microphone is upstream's own control | | Knowledge work | Knowledge is promoted to a top level row; `/workspace/knowledge` is unchanged and `/workspace` now redirects straight to it | | Cowork and the coding agent | The `/v1/agent/*` API is untouched. The workspace moves from a corner link to a sidebar row; the application behind it is the same one | ## Security note `apps/agent-console/middleware.ts` moves from `frame-ancestors 'none'` to `frame-ancestors 'self'`, and `X-Frame-Options` from `DENY` to `SAMEORIGIN`, because the chat shell now frames it from the same origin through the same Caddy listener. Everything the previous value defended against, a third-party page framing this one to harvest a click or a session, is still refused. `apps/web-console` keeps `'none'`. ## Buglog entry ```json {"id":"owui-fork-literal-rewrites","date":"2026-08-17","area":"deploy/docker","error_message":"open-webui bundle no longer matches hive_ui_surfaces.py; a removed surface may have come back: sidebar-playground-item, changelog-modal, about-vendor-social-badges","root_cause":"The exact-literal bundle rewrites match on minified identifiers, which the minifier allocates per chunk. Building the frontend from vendored source renames them, so three of nineteen rewrites stopped matching even though every surface was still present. An identifier-tolerant regex is not a fix: for changelog-modal and about-vendor-social-badges it matched sibling components with identical compiled shape, which would have neutered Settings or the admin dialogue.","fix":"Remove those surfaces in the vendored source instead, and order the Dockerfile so the rewrite layer still runs against the upstream bundle before the source build replaces it. Retiring the layer entirely is the follow-up.","tags":["owui","fork","bundle-patch","minifier","docker"]} ```






Summary
Two audience-visible demo-surface defects, two commits.
#846, dead Admin Panel entry. The chat user menu's "Admin Panel" item
navigates to
/admin, whichCaddyfile.owuialready 404s outright:.wolf/decisions.mdD-014 makes web-console/control-plane the sole adminsurface and keeps Open WebUI's own admin panel off. The entry was the one
thing in
deploy/docker/owui-patches/hive_ui_surfaces.pykept alive onpurpose (a
GUARDSassertion), on the theory that Hive's tenant-ownerpromotion made it a real control. It never was. Decision: remove, not
wire up. Wiring the endpoint would stand up a second admin surface next
to web-console's, which is the exact duplication D-014 forecloses. Same
removal shape as the neighboring Playground entry: an unconditional
!1gate, not a permission narrowing (every tenant owner already passes
role === "admin", so a permission-scoped hide would hide nothing)./api/v1/terminals/404ing on every page load is a different class, not abug:
Caddyfile.owuialready documents it as a deliberate block (#770, anauthenticated request-forgery primitive with no allowlist), and the front
end already degrades to an empty terminal list on any non-2xx. Left alone.
#848, demo-account test litter. Root cause: two currently-merged live
coverage paths silently defaulted their sign-in identity to
demo@hive-demo.invalid, the account the owner demos to prospects.apps/web-console/tests/e2e/_probe/agent-workspace-flows.spec.tsdefaulted to the demo account whenever
HIVE_QA_AGENT_EMAILwas unset.Every run past that default creates one real, currently undeletable
agent task (no per-task delete route exists yet), which is why the task
list is a wall of
interaction-coverage proof ...rows stuck Cancelledor Blocked.
.github/workflows/chat-coverage.ymlfell back tosecrets.HIVE_QA_TESTER_EMAILwheneversecrets.HIVE_QA_AGENT_EMAILwas unset, which it always was (no such secret exists on this repo).
The sweep's own ledger proves the result:
"demo@hive-demo.invalid -> hive-coverage-77811 survived a reload"(
docs/proof/chat-interaction-coverage-2026-08-10/coverage.run.json).Both now fail loudly instead of defaulting (the same
requireMintEnvpattern the first suite already used for its Supabase mint variables, and
the loud "inputs the sweep cannot run without" check the workflow already
had).
docs/live-test-auth.mdgets a new section stating the rule for thenext suite: read-only checks may use the demo account; anything that sends
a message, submits a task, or mints a key must use a dedicated identity,
the same way the plain E2E suite already isolates itself with
E2E_RUN_KEY-scoped fixture accounts.Not fixed here, flagged instead of silently skipped: neither change
provisions the dedicated identity itself.
HIVE_QA_AGENT_EMAILhas novalue to fail over to; standing one up needs live Supabase admin
credentials this worktree does not hold. Both suites now fail until that
identity is provisioned and the secret is set, which is the intended
consequence, not a regression: a workflow that used to succeed quietly
against the wrong account now requires the right one first.
Existing litter: enumerated, not deleted
Nothing is deleted in this PR. Per standing instruction, deletion needs an
explicit owner go-ahead after review of what's actually on the account.
Known rows, from issue #848, issue #858's 2026-08-11 demo-readiness walk,
and today's (2026-08-16) live reconfirmation:
New Chat. Earlier snapshots(Demo chat account is full of test litter, and nothing cleans up after the runs that create it #848, filed against the 2026-08-10 sweep) also show titled rows matching
synthetic composer-proof text (
Reply with exactly: ALPHA,SIERRA,PONYTAILTEST42,ping) plushive-coverage-77811-style generatednames from the coverage ledger.
interaction-coverage proof <timestamp> <uuid>(literal source:agent-workspace-flows.spec.tsline ~504) and,per Demo readiness walk 2026-08-11: no go as staged, four blockers an audience would see #858,
cancel-guard proof ..., sitting in Blocked or Cancelledstates.
interaction gate probe/interaction gatekeys (Demo readiness walk 2026-08-11: no go as staged, four blockers an audience would see #858 lists six).interaction-gate@example.invalid.On the 25
New Chatrows specifically: I cannot independentlyre-enumerate the live account from this worktree (no browser tool, no
Supabase/DB access available to this agent), so this is a proposed
classification, not an executed one. Proposed criterion before any delete:
pull each
New Chatrow'screated_atand first message. A row whosefirst message is a synthetic literal already used by a coverage suite
(the
Reply with exactly: <WORD>/pingfamily, or any string a suitesends deterministically) is automation litter regardless of its stuck
title; OWUI's title generation is a second, async LLM call after the first
response, and a suite that asserts the message round-trip and moves on
without waiting for or asserting a title is exactly the shape that leaves
a chat stuck on
New Chatforever, so I'd expect most or all of the 25 tobe this, not failed title-gen on a real conversation. A row whose first
message is not a known synthetic literal, or whose
created_atdoes notcluster with a known CI/coverage run, should be treated as a real
conversation with a failed title generation and left alone. I'm not able
to run that classification against the live rows myself; whoever has
live account access should run it before anything is deleted, and I'd
want to see the actual per-row list before endorsing any deletion set.
Also worth flagging, not part of this PR's diff: the in-flight, unmerged
test/interaction-coverage-gatebranch (issue #885, PR #811) is a newerconsole-wide interaction-coverage framework that per #885 has already
moved toward blocking mutating requests. The
interaction gate probeAPIkeys and the
interaction-gate@example.invalidspend alert most likelypredate that safeguard. Whoever owns that PR should confirm it also never
authenticates as the demo account once merged, same rule as this PR.
Visual proof
Not captured. This agent's toolset (Read/Write/Edit/Bash, no browser or
screenshot tool) cannot drive the running stack to capture the user-menu
before/after state orchestrator rule 8 requires before merge. Flagging
this as a structural capability gap rather than skipping it silently: a
Playwright-capable agent needs to capture the corrected user menu (Admin
Panel entry gone) against a real deployment before this merges.
Buglog entry
{"date":"2026-08-16","tags":["owui","chat","cowork","test-litter","demo-account","ci"],"error_message":"Demo account (demo@hive-demo.invalid) accumulates permanent chat, agent-task and API-key rows from live coverage suites; user menu also offered a dead Admin Panel entry (404 on every path)","root_cause":"Two live-coverage code paths silently defaulted their sign-in identity to the shared demo account: agent-workspace-flows.spec.ts's AGENT_EMAIL fallback and chat-coverage.yml's HIVE_QA_AGENT_EMAIL-to-HIVE_QA_TESTER_EMAIL secret fallback (which resolved to the demo identity in practice, per its own coverage ledger). Separately, hive_ui_surfaces.py deliberately kept the OWUI Admin Panel user-menu entry alive via a GUARDS assertion, while Caddyfile.owui already 404s every path it points at (D-014: web-console/control-plane is the sole admin surface).","fix":"Removed the Admin Panel entry from hive_ui_surfaces.py (moved from GUARDS to REWRITES, same !1 gate as the neighboring Playground entry). Changed both silent demo-account fallbacks to hard failures (agent-workspace-flows.spec.ts's requireMintEnv, chat-coverage.yml's HIVE_QA_AGENT_EMAIL secret) so a live coverage run can no longer land on the demo account by omission. Existing litter enumerated for owner review, not deleted; a dedicated non-demo QA identity still needs to be provisioned and wired to HIVE_QA_AGENT_EMAIL as a follow-up.","tags":["owui","chat","cowork","test-litter","demo-account","ci"]}Test plan
python3 scripts/test_owui_ui_surfaces.py— passpython3 scripts/test_caddy_owui_blocklist.py— pass (unchanged, confirms terminals/admin blocks untouched)python3 scripts/test_owui_rag_env_config.py— pass (unrelated suite, sanity check for the same patch package)python3 -c "import yaml; yaml.safe_load(open('.github/workflows/chat-coverage.yml'))"— valid YAMLagent-workspace-flows.spec.ts(docker compose run --build web-console npm run build) — not run in this worktree, nonode_modules/Docker invoked here; the diff is a mechanical mirror of the already-passingrequireMintEnvpattern in the same file, manually reviewed insteadchat-coverage.yml/agent-workspace-flows.spec.tsagainst a provisionedHIVE_QA_AGENT_EMAIL— blocked on that identity not existing yet