koala73/worldmonitor · error
Cloud fallback blocked for
Error message
Cloud fallback blocked for ${target} What it means
installRuntimeFetchPatch patches window.fetch in the Tauri desktop app so /api/* requests hit the local Node sidecar first and fall back to the remote cloud API when local fails. cloudFallback throws 'Cloud fallback blocked for <target>' when a fallback attempt is disallowed: either the target is local-only (isLocalOnlyApiTarget), the request is non-retryable (canRetryRequest, e.g. non-GET or streaming body), or the WORLDMONITOR_API_KEY secret is absent/invalid (secret-state check at src/services/runtime.ts:398-404).
Solutions
- Configure WORLDMONITOR_API_KEY in the desktop app's secret store so key-gated cloud fallback is allowed (check getSecretState('WORLDMONITOR_API_KEY').present/valid).
- If the target is intentionally local-only, don't expect cloud fallback — fix the local sidecar (is it running, correct port/token?) instead.
- Use a retryable request shape (GET without streaming body) or restructure the call so canRetryRequest passes.
- If the error appears only at cold start, the 2s secretsReady race may be too short — retry the request after startup completes or increase the grace period.
- Inspect with wm-debug-log=1 to see whether the block was due to allowCloudFallback or canRetryRequest and address the specific gate.
Example fix
// before
if (!allowCloudFallback || !canRetryRequest(input, init)) {
throw new Error(`Cloud fallback blocked for ${target}`);
}
// after
if (!allowCloudFallback || !canRetryRequest(input, init)) {
if (debug) console.log(`[fetch] cloud fallback blocked for ${target}: allow=${allowCloudFallback} retryable=${canRetryRequest(input, init)}`);
throw new Error(`Cloud fallback blocked for ${target}`);
} Defensive patterns
Strategy: validation
Validate before calling
const keyState = getSecretState('WORLDMONITOR_API_KEY');
const canFallBack = keyState.present && keyState.valid
&& !isLocalOnlyApiTarget(target)
&& canRetryRequest(input, init);
if (!canFallBack) {
// don't rely on cloud fallback; surface local error or prompt for key setup
} Type guard
function cloudFallbackAllowed(target: string, input: RequestInfo | URL, init?: RequestInit): boolean {
if (isLocalOnlyApiTarget(target)) return false;
if (!canRetryRequest(input, init)) return false;
const s = getSecretState('WORLDMONITOR_API_KEY');
return !!(s.present && s.valid);
} Try / catch
try {
return await patchedFetch('/api/some-endpoint');
} catch (err) {
if (err.message.startsWith('Cloud fallback blocked')) {
promptApiKeySetup(); // or show local-only error state
return offlineResponse();
}
throw err;
} Prevention
- Configure and validate WORLDMONITOR_API_KEY in the desktop secret store before using key-gated endpoints
- Never assume cloud fallback exists for config/secret routes — they are local-only by design
- Keep requests retryable (GET, non-streaming body) when cloud fallback matters
- Extend the secretsReady grace window or await secrets before first API call at cold start
- Enable wm-debug-log=1 when diagnosing which gate blocked the fallback
When it happens
Trigger: A /api/* request fails locally and code calls cloudFallback() when: (1) target is a local-only sidecar route (config/secrets endpoints) that must never proxy to cloud; (2) the request method/body makes it non-retryable; (3) the key-gated target requires WORLDMONITOR_API_KEY but the native secret state reports not present or not valid (or secretsReady timed out after 2s and the check threw).
Common situations: User running the desktop app without a configured WORLDMONITOR_API_KEY while requesting a key-gated endpoint whose local sidecar call failed; requesting a secrets/config route (by design local-only) that returned a non-ok status; sending POST/PUT with a body that can't be safely replayed to the cloud; secrets cache not yet initialized within the 2-second grace window at startup.
Related errors
- Failed to load status
- API key required
- API key required
- Convex embed key validation unavailable: fetch-error
- DNS lookup failed: status
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-22).
Data as JSON: /api/errors/44e3ed08f9259211.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/runtime.ts:409
if (debug) console.log(`[fetch] intercept → ${target}`);
let allowCloudFallback = !isLocalOnlyApiTarget(target) && canRetryRequest(input, init);
if (allowCloudFallback && !isKeyFreeApiTarget(target)) {
try {
const { getSecretState, secretsReady } = await import('@/services/runtime-config');
await Promise.race([secretsReady, new Promise<void>(r => setTimeout(r, 2000))]);
const wmKeyState = getSecretState('WORLDMONITOR_API_KEY');
if (!wmKeyState.present || !wmKeyState.valid) {
allowCloudFallback = false;
}
} catch {
allowCloudFallback = false;
}
}
const cloudFallback = async () => {
if (!allowCloudFallback || !canRetryRequest(input, init)) {
throw new Error(`Cloud fallback blocked for ${target}`);
}
const cloudUrl = `${getRemoteApiBaseUrl()}${target}`;
if (debug) console.log(`[fetch] cloud fallback → ${cloudUrl}`);
return nativeFetch(input instanceof Request ? new Request(cloudUrl, input) : cloudUrl, init);
};
try {
const t0 = performance.now();
const response = await fetchLocalWithStartupRetry(target, input, init);
if (debug) console.log(`[fetch] ${target} → ${response.status} (${Math.round(performance.now() - t0)}ms)`);
if (!response.ok) {
if (!allowCloudFallback) {
if (debug) console.log(`[fetch] local-only endpoint ${target} returned ${response.status}; skipping cloud fallback`);
return response;
}
if (debug) console.log(`[fetch] local ${response.status}, falling back to cloud`);
return cloudFallback();View on GitHub (pinned to e586b8b4b8)