koala73/worldmonitor · warning
Failed to load status
Error message
Failed to load status
What it means
ServiceStatusPanel.fetchStatus throws 'Failed to load status' when fetchServiceStatuses() returns a payload that is not success or contains zero services AND the panel has no previously-loaded good data to fall back on (src/components/ServiceStatusPanel.ts:45). It represents the first-load failure case: there is nothing to render, so the panel raises to switch into its error rendering path (caught immediately in the same method, setting this.error).
Solutions
- Check the service-status API endpoint responds with { success: true, services: [...] } (curl the endpoint in the same environment; verify api/ route and any worker feeding it).
- Confirm backend/worker deployment health — an empty services list usually means the status collector hasn't run or returned nothing; check worker logs/seed keys.
- Verify network reachability and correct base URL for the API in the environment (desktop: local sidecar running; web: same-origin /api).
- Handle the panel's error state gracefully in UI — this throw is caught internally and rendered as 'Failed to load', so ensure callers of mountPanel/fetchStatus don't double-handle.
- If data is intermittent, add a retry/backoff around fetchServiceStatuses before first successful render.
Example fix
// before
if (!data.success || data.services.length === 0) {
if (this.services.length > 0) { keptLastGood = true; return false; }
throw new Error('Failed to load status');
}
// after
if (!data.success || data.services.length === 0) {
if (this.services.length > 0) { keptLastGood = true; return false; }
this.error = t('common.failedToLoad');
console.warn('[ServiceStatus] empty/unsuccessful status payload:', data);
return true; // render error state without throwing
} Defensive patterns
Strategy: fallback
Validate before calling
const data = await fetchServiceStatuses();
if (!data?.success || !Array.isArray(data.services) || data.services.length === 0) {
// render empty/error state before the panel throws
} Type guard
function isServiceStatusPayload(v: unknown): v is { success: true; services: Array<{ name: string; status: string; category: string }> } {
const d = v as any;
return !!d && d.success === true && Array.isArray(d.services) && d.services.length > 0;
} Try / catch
const ok = await panel.fetchStatus(); // method catches internally
if (!ok && panelHasErrorState) renderErrorPlaceholder('Status unavailable'); Prevention
- Verify the service-status endpoint returns success:true with non-empty services before mounting the panel
- Keep last-good data in the panel so transient failures degrade gracefully instead of throwing on first load
- Monitor the status worker so an empty services list is detected server-side
- Add a retry with backoff before declaring first-load failure
When it happens
Trigger: Calling fetchStatus() on first mount (services array empty) when the /api service-status fetch returns { success: false } or an empty services list — e.g. the status endpoint is down, returns an error envelope, or the upstream status aggregator yields no services. If services.length > 0 the panel silently keeps last-good data instead of throwing.
Common situations: Panel mounted while the backend/API route is misconfigured or unreachable (404/500 wrapped as success:false); the status worker hasn't produced any services yet on a fresh deployment; network offline at first paint so the fetch helper resolves with a failure envelope rather than throwing.
Related errors
- Cloud fallback blocked for
- Convex embed key validation unavailable: fetch-error
- DNS lookup failed: status
- HTTP
- Inputs changed while reading. Select the file again to…
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-22).
Data as JSON: /api/errors/b2e35c58dabd25cb.
Report an issue: GitHub.
Appendix: source
Thrown at src/components/ServiceStatusPanel.ts:45
private error: string | null = null;
private filter: CategoryFilter = 'all';
constructor() {
super({ id: 'service-status', title: t('panels.serviceStatus'), showCount: false, infoTooltip: t('components.serviceStatus.infoTooltip') });
}
private lastServicesJson = '';
public async fetchStatus(): Promise<boolean> {
let keptLastGood = false;
try {
const data = await fetchServiceStatuses();
if (!this.element?.isConnected) return false;
if (!data.success || data.services.length === 0) {
if (this.services.length > 0) {
keptLastGood = true;
return false;
}
throw new Error('Failed to load status');
}
const fingerprint = data.services.map(s => `${s.name}:${s.status}`).join(',');
const changed = fingerprint !== this.lastServicesJson;
this.lastServicesJson = fingerprint;
this.services = data.services;
this.error = null;
return changed;
} catch (err) {
if (this.isAbortError(err)) return false;
if (!this.element?.isConnected) return false;
this.error = t('common.failedToLoad');
console.error('[ServiceStatus] Fetch error:', err);
return true;
} finally {
this.loading = false;
if (this.element?.isConnected && !keptLastGood) {
this.render();View on GitHub (pinned to e586b8b4b8)