Fix three agents/identity dogfood UX defects: KC account-console loop, Agents 'starting…' stall, broken agent console - #34
Open
baron-3dl wants to merge 5 commits into
Open
Conversation
…he Funnel-fronted edge Clicking "Account & password" dropped users into Keycloak's account console, which then reload-looped endlessly. Root cause: the gateway VM's Caddy :8081 block — the plain-HTTP block Tailscale Funnel forwards browsers to after terminating their TLS — reported X-Forwarded-Proto: http to Keycloak. KC treated the request as non-secure and stripped Secure/SameSite=None from its SSO cookies (AUTH_SESSION_ID, KEYCLOAK_IDENTITY, ...). Those cookies were then not sent in the account console's redirect-based re-auth, so check-sso never found a session and the console bounced through the auth endpoint forever. LibreChat and the portal oauth2-proxy dodged the same edge by disabling Secure app-side, but the account console needs SameSite=None (which requires Secure), so the only correct fix is to assert the true public scheme at the edge — which deploy/README.md already named as the proper fix and nobody had applied. Verified live: with X-Forwarded-Proto: https, KC's auth endpoint sets AUTH_SESSION_ID=...;Secure;SameSite=None; without it, SameSite=Lax and no Secure plus a "Non-secure context detected" WARN. Only the :8081 block needs it; the :8443 LAN backchannel terminates its own TLS and already reports https. enterpriseaiframework-4f1 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d stops reading as stuck The Agents list only refetched on a tab switch or an explicit action — never while the tab was being watched. So an agent you just created (or just wired a connector to) sat at "starting…" until you navigated away and back, even though its pod had gone Running seconds later. A working boot that reads as a stuck one is the finding-43 failure mode; a dogfood user hit exactly it. While the Agents tab is on screen and any agent is still `starting`, re-poll (4s) until nothing is transitioning; stop when the tab is left or the browser tab is backgrounded, and refetch once on return (a hidden tab throttles the timer to a stall). Verified live against the deployed control plane: the agents API already reports rudi as running with the pod Running — the gap was purely that the page never re-asked. enterpriseaiframework-7af Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…onsole is not a broken shell
opencode serves the console with a strict CSP whose `script-src` has no
`'unsafe-inline'` — it whitelists its own one inline script by sha256 and nothing
else. The proxy injects the URL-rewriting shim as an inline script; the browser,
finding no matching hash, silently refused to run it. With the shim dead, the
compiled bundle resolved its server as `location.origin` with no path, so every
runtime request — `/api/event`, `/config`, the pty socket — hit the portal origin
ROOT unprefixed and 404'd. `/api/event` reconnected in a tight loop and the
console was a broken shell (the "Open console" breakage a dogfood user reported).
Fix: when the entry document carries a CSP, extend its `script-src` (or add one
that mirrors `default-src`) with the shim's own sha256 — the same mechanism
opencode admits its own inline script by. Nothing else inline is permitted;
opencode's policy is preserved in full. Stripping the CSP was rejected: this
surface proxies an unattended agent holding a spendable key.
Verified end-to-end in Chromium against the real rudi daemon (app run on
loopback, real Basic-auth hop): before, `window.fetch` unwrapped and
`EventSource('/api/event').url` pointed at the origin root with a 404 flood;
after, the shim runs, the URL is rewritten to `/agents/rudi/api/event`, the SSE
stream OPENS (readyState 1), `/config` returns 200, and there are zero CSP
refusals. Hermetic test asserts the authorised hash equals the hash of the exact
injected script bytes, so shim text and CSP can never drift apart.
enterpriseaiframework-f4c
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| out = agent_console._authorise_shim_in_csp("default-src 'self' https://cdn.example", "bot") | ||
| script = next(d.strip() for d in out.split(";") if d.strip().lower().startswith("script-src")) | ||
| parts = script.split() | ||
| assert "'self'" in parts and "https://cdn.example" in parts and src in parts, out |
…B contexts build `kubectl apply` records a full copy of the object in a last-applied-configuration annotation, and annotations cap at 256KiB — so any build context between ~256KB and the ~1MiB ConfigMap limit this script actually guards (700KB) failed at ConfigMap creation with "metadata.annotations: Too long". Hit building the 289KB control-plane image. The ConfigMap name is unique per build, so there is nothing to reconcile; plain `create` avoids the annotation entirely. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ank page
The CSP fix let the shim run, but the console was STILL a mostly-blank page — a
toolbar over an empty <main>. Root cause (isolated in a real browser): opencode's
console is an SPA whose router reads location.pathname to choose a view, and —
like its asset and server URLs — it assumes it is mounted at the origin ROOT.
Under /agents/<name>/ the initial path matches no route, so it renders an empty
<main>. Proven by loading the SAME daemon directly: at / it renders the full home
("Projects / Add project / Settings / Help / Create a session to get started");
at /agents/<name>/ the identical daemon renders an empty <main>.
The shim now also strips the prefix from location.pathname before the bundle runs
(so the router initialises at root), interposes on history.pushState/replaceState
to keep every navigation prefixed in the address bar, and re-adds the prefix after
load — so a reload lands back on the console and not on the chat surface at the
origin root. The CSP hash covers these bytes automatically (single source in
_shim_js).
Verified in a real browser against the real daemon, through the app: <main>
renders the home view on open AND after a reload, address bar stays at
/agents/<name>/, zero CSP refusals, no JS errors.
Known follow-ups (separate, not blocking): the CSS-loaded font /assets/Inter.ttf
404s at the origin root (cosmetic; shim rewrites JS-initiated requests only, not
CSS url()), and browser back/forward is not yet prefix-aware.
enterpriseaiframework-f4c
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Defects hit while dogfooding the agents surface, root-caused and fixed. Each verified against the live stack — the agent console fix through the full public path (Funnel → Caddy → oauth2-proxy → control-plane → daemon) with a real browser login.
1. "Account & password" → Keycloak reload loop (
enterpriseaiframework-4f1)Behind Tailscale Funnel, TLS terminates before the plain-HTTP Caddy
:8081block, which forwardedX-Forwarded-Proto: http; Keycloak strippedSecure/SameSite=Nonefrom its SSO cookies and the account console re-auth loop never found a session.Fix:
header_up X-Forwarded-Proto httpson the/realms/*+/resources/*edge routes. Deployed to the gateway VM.2. Agents tab stuck at "starting…" (
enterpriseaiframework-7af)The list only refetched on a tab switch. Fix: bounded poll while the Agents tab is shown and any agent is
starting; stops when settled/hidden; refetches on refocus.3. "Open console" → mostly blank page (
enterpriseaiframework-f4c) — two root causesopencode's console is an SPA that assumes it is served at the origin root.
script-src(no'unsafe-inline') silently dropped the proxy's injected URL-rewriting shim, so runtime requests escaped the/agents/<name>prefix and/api/event404-looped. Fix: extend the forwarded CSP'sscript-srcwith the shim's own sha256.location.pathnameand matched no route under the prefix → empty<main>(the blank page). Proven by loading the same daemon directly: full home at/, empty<main>at/agents/<name>/. Fix: the shim strips the prefix on load so the router inits at root, keeps navigations prefixed via History interposition, and re-adds the prefix after load (reload-safe).Verified live through the public path (real login):
<main>renders opencode's home (Projects / Add project / Settings / Help / Create a session…) on open and after a reload; zero CSP refusals.Follow-ups (tracked, non-blocking,
enterpriseaiframework-42a): CSS-loaded fontInter.ttf404 (cosmetic), back/forward not prefix-aware.Deploy
4996e9c); also ship via merge →watch-and-deploy.Tests
test_caddy_keycloak_forwarded_proto.py,test_agents_status_poll.py, and four hermetic cases intest_agent_console.py(CSP hash equals the hash of the exact injected shim bytes; router prefix-stripping wired). Plus akaniko-build.shfix (contexts >256KB failed onkubectl apply's annotation limit).🤖 Generated with Claude Code