Relationships
#2504 dashboard: Schedules/Webhooks views are empty for non-admins -- the health stream needs admin on access:* and the 403 is swallowed
Opened by randybias · 9/24/2026· Shipped 9/28/2026
Problem
For any dashboard user who is not an administrator, the Schedules and Webhooks views, and the overview's health pill, are empty. The Schedules view says "No schedules configured" even when schedules are registered and firing.
Cause (read at ad0aa46, measured on 20260923.231117.0)
- These views read the
healthobject thatf0()streams from/api/v1/health/stream(SSE). - That endpoint calls
authenticateAdmin(src/cli/commands/serve.ts:4696), which requiresadminonaccess:*(src/serve/admin_auth.ts:140-148). - A non-admin token gets
HTTP 403 {"message":"Access denied: requires admin permission"}. - The dashboard's stream reader swallows the error (
catch{}), and the views render their empty state.
Measured with two tokens against the same server:
| token | /api/v1/health/stream |
Schedules view |
|---|---|---|
| admin | 200, text/event-stream, 3 events in 14 s, scheduling.schedules lists mircloud-sweep-cabling-drift 0 5 * * * |
shows it |
operator (grants run, read, approve on workflow:*; read on model:*) |
403 | "No schedules configured" |
GET /health (unauthenticated) returns the same scheduling block, so the data is not secret.
Why it matters
The only permission that unlocks the view is full admin on access:*, which also allows minting tokens and changing grants. An operator who can run and approve workflows cannot see when the next scheduled run fires, and the dashboard tells them nothing is scheduled. That is worse than an error.
Ask
- Gate the health stream (or at least its
schedulingandwebhooksparts) onread, notadmin. - When a view's data request is refused, show "access denied", never the empty state.
Shipped
Click a lifecycle step above to view its details.
stack72 commented 9/28/2026, 10:33:34 PM
Thanks @randybias for reporting this! We shipped: Let any valid token read the health snapshot and stream by splitting a token-only authenticateToken out of authenticateAdmin. Register each SSE health stream as a token session in the existing registry so revoke, rotate, expiry and principal deprovisioning end it through the same paths as WebSocket sessions, and cap open streams per token at 10 with a 429 above it. Make the dashboard health stream report denied and retry transient failures, and show Access denied or Loading rather than the empty state.. The fix has been merged and a release is on its way. We appreciate your contribution to swamp.
Sign in to post a ripple.