Client or integration
OpenCodex dashboard
Area
Dashboard
Summary
Three problems that leave the dashboard blank, slow, or misleading. The first is the severe one: on hosts where icacls hardening cannot be verified, the entire management API fails closed and the dashboard shows no data at all.
1. Management API fails closed when ACL hardening cannot complete.
initializeManagementAuthState() needs a token, and every path to one requires hardenSecretPath(..., { required: true }) to succeed — including readExistingToken(), so pre-creating the token file does not help. On this host hardenSecretDir() consumes its whole budget and returns ok: false, so the state becomes available: false and every /api/* request returns 503 {"error":"management API unavailable"}. The GUI loads and then shows nothing.
What makes this look like a bug rather than the host's fault:
icacls itself is not slow here. Running the same grant / /inheritance:r / verify sequence manually against a directory completes in ~220ms total, well inside the 5s budget, and spawning icacls from within bun measures ~35ms. I could not identify what consumes the budget.
- Raising
OPENCODEX_ACL_TIMEOUT_MS to 30000 does not help. Lowering it to 1000 just fails faster (and is a real ~4.7s saving per process start, since loadConfig() hardens dir+config+auth sequentially).
- Elsewhere the codebase deliberately soft-fails ACL hardening so that it "cannot block OAuth logins or token refresh". The management token is the one place that fail-closes, and the cost is the entire dashboard.
2. The startup-health probe always times out, so a false warning is permanent.
startup-health-cache.ts spawns a full bun subprocess (__startup-health) every 30s with PROBE_TIMEOUT_MS = 5000. Here a bare bun -e 0 takes ~450ms and loadConfig() adds ~3s, so a subprocess that also loads the CLI entry never finishes inside 5s. The probe therefore always falls back to conservativeFallback() with diagnosticStale: true and the dashboard permanently shows "Could not read startup protection." — while the actual state is healthy (status: native, rebootSafe: true).
The stale-while-revalidate design correctly never blocks a request, but it also means a bun process is spawned and discarded every 30s forever and the warning never clears.
3. GET /api/settings gates the dashboard's first paint.
/api/settings calls resolveCodexRuntime() for "full alternative discovery (memoized) so newerAvailable warnings work". First call: 6-10s here. Subsequent calls: 2ms. dashboard-core-poll.ts fetches five endpoints in one Promise.all, so Status, Version, Uptime and Providers all stay blank and the header reads Offline until the slowest one returns. Opening the GUI shortly after a service start shows "Offline" for a long stretch while the proxy is already serving traffic.
4. Smaller: the request log has no surface filter.
/api/logs returns every entry regardless of surface, and the Logs page treats only surface === "claude" as Claude, so claude-desktop requests appear under the Codex tab. A ?surface= query parameter plus including claude-desktop in the Claude bucket fixes both ends.
Reproduction
Problem 1:
- On a host where
icacls hardening cannot be verified, start the proxy. ~/.opencodex/service.log fills with ACL hardening timed out (ETIMEDOUT) ... (budget exhausted).
GET http://127.0.0.1:10100/healthz -> 200.
GET http://127.0.0.1:10100/api/logs -> 503 {"error":"management API unavailable"}.
- Open the dashboard: it renders but every panel is empty.
- Calling
initializeManagementAuthState() directly reports reason: management token directory ACL hardening did not complete.
- Setting
OPENCODEX_ADMIN_AUTH_TOKEN makes it available: true, source: environment — it short-circuits before any filesystem work.
Problem 2: leave the dashboard open. The startup-protection warning never clears, and a new bun process appears every 30s. /api/settings reports startupHealth.diagnosticStale: true indefinitely.
Problem 3: restart the service, then immediately load the dashboard and time how long it reads "Offline". Timing the five batch endpoints individually shows /api/settings at 6-10s cold and 2ms warm while the others answer in single-digit milliseconds.
Version
2.7.43
Operating system
Windows 11 (build 10.0.26200), npm -g install, scheduler service backend. A third-party antivirus does on-access scanning, which is relevant to problems 2 and 3 because both assume subprocess spawns are cheap.
Provider and model
Not provider-specific.
Logs or error output
[opencodex] ACL hardening timed out (ETIMEDOUT) - transient icacls stall; the volume may still
support per-user NTFS ACLs; ACL state unverified (budget exhausted) - continuing without NTFS ACL harden
GET /api/logs -> 503
{ "error": "management API unavailable" }
initializeManagementAuthState():
available: false
reason : management token directory ACL hardening did not complete
Measured latency of the dashboard's Promise.all batch, cold:
/healthz 233 ms
/api/providers 6 ms
/api/settings 10 107 ms <- gates the first paint
/api/sidecar-settings 16 ms
/api/shadow-call-settings 1 ms
Redacted configuration
{ "port": 10100, "hostname": "127.0.0.1", "claudeCode": { "enabled": true, "authMode": "proxy" } }
isApiAuthRequired(config) is false here (loopback hostname), so the session-issuing path is not the blocker — only state.available is.
Suggested fixes
- Treat unverified ACL state as a warning for the management token as well, the way the OAuth paths already do, rather than disabling the whole management API. Failing closed costs the user the entire dashboard on hosts where
icacls is unreliable, which seems worse than proceeding with a warning.
- Collect startup health in-process where possible, or back off after repeated probe timeouts instead of re-spawning every 30s — and distinguish "could not determine" from "unprotected" in the UI.
- Return
/api/settings without the discovery result and fill newerAvailable from a background refresh, or let the dashboard render as soon as /healthz answers rather than gating on the whole batch.
- Add
?surface= to /api/logs and include claude-desktop in the Claude bucket in the Logs page.
Checks
Client or integration
OpenCodex dashboard
Area
Dashboard
Summary
Three problems that leave the dashboard blank, slow, or misleading. The first is the severe one: on hosts where
icaclshardening cannot be verified, the entire management API fails closed and the dashboard shows no data at all.1. Management API fails closed when ACL hardening cannot complete.
initializeManagementAuthState()needs a token, and every path to one requireshardenSecretPath(..., { required: true })to succeed — includingreadExistingToken(), so pre-creating the token file does not help. On this hosthardenSecretDir()consumes its whole budget and returnsok: false, so the state becomesavailable: falseand every/api/*request returns503 {"error":"management API unavailable"}. The GUI loads and then shows nothing.What makes this look like a bug rather than the host's fault:
icaclsitself is not slow here. Running the same grant //inheritance:r/ verify sequence manually against a directory completes in ~220ms total, well inside the 5s budget, and spawningicaclsfrom within bun measures ~35ms. I could not identify what consumes the budget.OPENCODEX_ACL_TIMEOUT_MSto 30000 does not help. Lowering it to 1000 just fails faster (and is a real ~4.7s saving per process start, sinceloadConfig()hardens dir+config+auth sequentially).2. The startup-health probe always times out, so a false warning is permanent.
startup-health-cache.tsspawns a full bun subprocess (__startup-health) every 30s withPROBE_TIMEOUT_MS = 5000. Here a barebun -e 0takes ~450ms andloadConfig()adds ~3s, so a subprocess that also loads the CLI entry never finishes inside 5s. The probe therefore always falls back toconservativeFallback()withdiagnosticStale: trueand the dashboard permanently shows "Could not read startup protection." — while the actual state is healthy (status: native,rebootSafe: true).The stale-while-revalidate design correctly never blocks a request, but it also means a bun process is spawned and discarded every 30s forever and the warning never clears.
3.
GET /api/settingsgates the dashboard's first paint./api/settingscallsresolveCodexRuntime()for "full alternative discovery (memoized) so newerAvailable warnings work". First call: 6-10s here. Subsequent calls: 2ms.dashboard-core-poll.tsfetches five endpoints in onePromise.all, so Status, Version, Uptime and Providers all stay blank and the header reads Offline until the slowest one returns. Opening the GUI shortly after a service start shows "Offline" for a long stretch while the proxy is already serving traffic.4. Smaller: the request log has no
surfacefilter./api/logsreturns every entry regardless of surface, and the Logs page treats onlysurface === "claude"as Claude, soclaude-desktoprequests appear under the Codex tab. A?surface=query parameter plus includingclaude-desktopin the Claude bucket fixes both ends.Reproduction
Problem 1:
icaclshardening cannot be verified, start the proxy.~/.opencodex/service.logfills withACL hardening timed out (ETIMEDOUT) ... (budget exhausted).GET http://127.0.0.1:10100/healthz-> 200.GET http://127.0.0.1:10100/api/logs->503 {"error":"management API unavailable"}.initializeManagementAuthState()directly reportsreason: management token directory ACL hardening did not complete.OPENCODEX_ADMIN_AUTH_TOKENmakes itavailable: true, source: environment— it short-circuits before any filesystem work.Problem 2: leave the dashboard open. The startup-protection warning never clears, and a new bun process appears every 30s.
/api/settingsreportsstartupHealth.diagnosticStale: trueindefinitely.Problem 3: restart the service, then immediately load the dashboard and time how long it reads "Offline". Timing the five batch endpoints individually shows
/api/settingsat 6-10s cold and 2ms warm while the others answer in single-digit milliseconds.Version
2.7.43
Operating system
Windows 11 (build 10.0.26200),
npm -ginstall,schedulerservice backend. A third-party antivirus does on-access scanning, which is relevant to problems 2 and 3 because both assume subprocess spawns are cheap.Provider and model
Not provider-specific.
Logs or error output
Measured latency of the dashboard's
Promise.allbatch, cold:Redacted configuration
{ "port": 10100, "hostname": "127.0.0.1", "claudeCode": { "enabled": true, "authMode": "proxy" } }isApiAuthRequired(config)isfalsehere (loopback hostname), so the session-issuing path is not the blocker — onlystate.availableis.Suggested fixes
icaclsis unreliable, which seems worse than proceeding with a warning./api/settingswithout the discovery result and fillnewerAvailablefrom a background refresh, or let the dashboard render as soon as/healthzanswers rather than gating on the whole batch.?surface=to/api/logsand includeclaude-desktopin the Claude bucket in the Logs page.Checks