Skip to content

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
mainfrom
worktree-kc-account-console-loop
Open

Fix three agents/identity dogfood UX defects: KC account-console loop, Agents 'starting…' stall, broken agent console#34
baron-3dl wants to merge 5 commits into
mainfrom
worktree-kc-account-console-loop

Conversation

@baron-3dl

@baron-3dl baron-3dl commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

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 :8081 block, which forwarded X-Forwarded-Proto: http; Keycloak stripped Secure/SameSite=None from its SSO cookies and the account console re-auth loop never found a session.
Fix: header_up X-Forwarded-Proto https on 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 causes

opencode's console is an SPA that assumes it is served at the origin root.

  • (a) CSP blocked the shim. opencode's script-src (no 'unsafe-inline') silently dropped the proxy's injected URL-rewriting shim, so runtime requests escaped the /agents/<name> prefix and /api/event 404-looped. Fix: extend the forwarded CSP's script-src with the shim's own sha256.
  • (b) The router assumed the root. Even with the shim running, the console's router reads location.pathname and 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 font Inter.ttf 404 (cosmetic), back/forward not prefix-aware.


Deploy

Tests

test_caddy_keycloak_forwarded_proto.py, test_agents_status_poll.py, and four hermetic cases in test_agent_console.py (CSP hash equals the hash of the exact injected shim bytes; router prefix-stripping wired). Plus a kaniko-build.sh fix (contexts >256KB failed on kubectl apply's annotation limit).

🤖 Generated with Claude Code

alice and others added 3 commits August 10, 2026 17:43
…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>
@baron-3dl baron-3dl changed the title Fix account console reload loop: assert https scheme to Keycloak at the Funnel-fronted edge Fix three agents/identity dogfood UX defects: KC account-console loop, Agents 'starting…' stall, broken agent console Aug 10, 2026
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
alice and others added 2 commits August 10, 2026 18:31
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants