koala73/worldmonitor · error
Cable health unavailable
Error message
Cable health unavailable
What it means
fetchCableHealth throws 'Cable health unavailable' inside the circuit-breaker execute callback when the RPC response from InfrastructureServiceClient.getCableHealth fails the isUsableResponse validity check (src/services/cable-health.ts:70). isUsableResponse requires a positive finite generatedAt within 90 minutes, a non-empty object-shaped cables map, and every record with valid lastUpdated/evidence fields — so the throw fires when the backend returns an empty, stale, or structurally invalid snapshot.
Solutions
- Verify the infrastructure RPC service returns a fresh snapshot: call getCableHealth directly against getRpcBaseUrl() and inspect generatedAt and cables contents.
- Check the cable-health data worker is running and writing valid records (positive generatedAt, populated evidence arrays); re-run its seed/refresh job.
- Confirm src/generated clients are up to date with proto (make generate) — schema drift yields default-valued fields that fail isUsableResponse.
- Check client clock/system time; a skewed clock invalidates the age window (age < 0 or > 90 min) and rejects otherwise-good data.
- If the response is valid but rejected, add logging in isUsableResponse to identify which predicate fails and fix the producer accordingly.
Example fix
// before
const response = await client.getCableHealth({});
if (!isUsableResponse(response)) throw new Error('Cable health unavailable');
return response;
// after
const response = await client.getCableHealth({});
if (!isUsableResponse(response)) {
console.warn('[cable-health] unusable snapshot:', { generatedAt: response?.generatedAt, cableCount: Object.keys(response?.cables ?? {}).length });
throw new Error('Cable health unavailable');
}
return response; Defensive patterns
Strategy: validation
Validate before calling
function looksLikeCableSnapshot(r: unknown): boolean {
const v = r as any;
return !!v && Number.isFinite(v.generatedAt) && v.generatedAt > 0
&& v.cables && typeof v.cables === 'object' && !Array.isArray(v.cables);
} Type guard
function isUsableCableHealth(r: unknown): r is GetCableHealthResponse {
const v = r as any;
if (!v || typeof v !== 'object') return false;
const age = Date.now() - v.generatedAt;
return Number.isFinite(v.generatedAt) && v.generatedAt > 0 && age >= 0 && age < 90 * 60 * 1000
&& v.cables && typeof v.cables === 'object' && !Array.isArray(v.cables);
} Try / catch
try {
const health = await fetchCableHealth();
renderCableHealth(health);
} catch (err) {
if (err.message === 'Cable health unavailable') renderCableLayerEmpty();
else throw err;
} Prevention
- Keep the cable-health producer running and refreshing generatedAt within the 90-minute window
- Regenerate proto clients after schema changes so fields don't deserialize as defaults
- Validate RPC base URL per environment before shipping
- Watch system clock skew in desktop environments; it invalidates the freshness window
When it happens
Trigger: client.getCableHealth({}) resolves but the response has generatedAt <= 0 or older than 90 minutes, cables missing/not an object, or any cable record lacking valid lastUpdated/evidence entries. Also thrown when breaker.execute itself fails validation so the emptyFallback is returned and the post-check path (line 74-78) finds no retained last-good response.
Common situations: Infrastructure worker down or seeded with zero cable records (empty cables map + generatedAt 0); RPC base URL misconfigured so the client hits a wrong endpoint returning an empty/default proto; clock skew or stale persisted cache making generatedAt appear older than the 90-minute retention window; proto schema drift after regeneration so fields arrive unset.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- Cyber threats unavailable
- Could not resolve to a country.
- country must be an ISO 3166-1 alpha-2 country code, e.g…
- countryCode must be a 2-letter ISO 3166-1 alpha-2 code
- get-intel-timeline: domain Required unless country is set…
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-22).
Data as JSON: /api/errors/48b8f28385e4db94.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/cable-health.ts:70
&& typeof e.ts === 'number' && Number.isFinite(new Date(e.ts).getTime())));
}
function retainedResponse(): CableHealthResponse | null {
if (!cachedResponse) return null;
const age = Date.now() - Date.parse(cachedResponse.generatedAt);
return age >= 0 && age < MAX_RETAINED_AGE_MS ? cachedResponse : null;
}
// ---- Public API ----
export async function fetchCableHealth(): Promise<CableHealthResponse> {
const now = Date.now();
const retained = retainedResponse();
if (retained && now < cacheExpiry) return retained;
const resp = await breaker.execute(async () => {
const response = await client.getCableHealth({});
if (!isUsableResponse(response)) throw new Error('Cable health unavailable');
return response;
}, emptyFallback, { shouldCache: isUsableResponse, staleRefreshMode: 'await' });
if (!isUsableResponse(resp)) {
const lastGood = retainedResponse();
if (lastGood) return lastGood;
throw new Error('Cable health unavailable');
}
const cables: Record<string, CableHealthRecord> = {};
for (const [id, proto] of Object.entries(resp.cables)) {
cables[id] = toRecord(proto);
}
const result: CableHealthResponse = {
generatedAt: new Date(resp.generatedAt).toISOString(),
cables,
};View on GitHub (pinned to e586b8b4b8)