koala73/worldmonitor · error · EmbedKeyUnavailableError
Convex embed key validation unavailable: fetch-error
Error message
Convex embed key validation unavailable: fetch-error
What it means
fetchFromConvex wraps its POST to `<convexSiteUrl>/api/internal-validate-embed-key` in a try/catch with a 3-second AbortSignal timeout. If fetch rejects — DNS failure, connection refused, TLS error, or the 3s timeout abort — it throws EmbedKeyUnavailableError with `fetch-error`, signaling the Convex validator could not be reached at all.
Solutions
- Verify CONVEX_SITE_URL is the correct `https://<deployment>.convex.site` URL and reachable: `curl -v $CONVEX_SITE_URL/api/internal-validate-embed-key`.
- Check for transient issues and retry — a 3s timeout can be exceeded during Convex cold starts; confirm the deployment is running.
- Verify network egress/DNS from the hosting environment (Railway/Edge) to *.convex.site is allowed.
- If timeouts recur under load, investigate Convex deployment performance or revisit the 3s budget deliberately rather than in an ad-hoc patch.
Example fix
// before: typo'd host times out CONVEX_SITE_URL=https://my-app.convex.cloud // after: correct HTTP actions host CONVEX_SITE_URL=https://my-app.convex.site
Defensive patterns
Strategy: retry
Validate before calling
let reachable = false;
try {
reachable = (await fetch(`${process.env.CONVEX_SITE_URL}/api/internal-validate-embed-key`, { method: 'HEAD', signal: AbortSignal.timeout(3_000) })).status !== undefined;
} catch { /* unreachable */ }
if (!reachable) throw new Error('Convex validator endpoint unreachable; check CONVEX_SITE_URL and network egress'); Try / catch
try {
const verdict = await result(keyHash);
} catch (err) {
if (err instanceof EmbedKeyUnavailableError && err.message.endsWith('fetch-error')) {
// transient network/timeout — retry once with backoff, then fail closed
await delay(500);
return retryValidation(keyHash).catch(() => failClosed());
}
throw err;
} Prevention
- Point CONVEX_SITE_URL at the https://<deployment>.convex.site HTTP actions host, not .convex.cloud.
- Add a health check that pings the internal endpoint before processing embed-key traffic.
- Allow reasonable retry with backoff for the 3s timeout; Convex cold starts can occasionally exceed it.
- Ensure the hosting environment's egress allows connections to *.convex.site over HTTPS.
When it happens
Trigger: The `await fetch(...)` inside the try block rejects: CONVEX_SITE_URL is unreachable/wrong-host, the network is down, DNS fails, or the request exceeds `AbortSignal.timeout(3_000)` and is aborted.
Common situations: Typo'd or stale CONVEX_SITE_URL (e.g. pointing at the deprecated .convex.cloud host instead of .convex.site); Convex deployment paused or deleted; transient network outage or cold-start latency exceeding the 3-second timeout; egress blocked from the worker's network.
Related errors
- COMPANY_MONITORING_CLASSIFICATION_FENCED
- callbackUrl DNS resolution failed: ${message}
- Authentication unavailable while loading MCP clients. Try ag
- Revoke service is temporarily unavailable. Try again in a mo
- Authentication unavailable while loading Business Pro seats.
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/886ee45b63f15a24.
Report an issue: GitHub.
Appendix: source
Thrown at server/_shared/embed-key.ts:155
const convexSharedSecret = process.env.CONVEX_SERVER_SHARED_SECRET;
if (!convexSiteUrl || !convexSharedSecret) {
throw new EmbedKeyUnavailableError('Convex embed key validation unavailable: missing-config');
}
let resp: Response;
try {
resp = await fetch(`${convexSiteUrl}/api/internal-validate-embed-key`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': 'worldmonitor-gateway/1.0',
'x-convex-shared-secret': convexSharedSecret,
},
body: JSON.stringify({ keyHash }),
signal: AbortSignal.timeout(3_000),
});
} catch {
throw new EmbedKeyUnavailableError('Convex embed key validation unavailable: fetch-error');
}
if (!resp.ok) {
throw new EmbedKeyUnavailableError(
`Convex embed key validation unavailable: http-${resp.status}`,
);
}
let value: unknown;
try {
value = await resp.json();
} catch {
throw new EmbedKeyUnavailableError('Convex embed key validation unavailable: invalid-json');
}
if (value === null) return null;
if (!isEmbedKeyResult(value)) {
throw new EmbedKeyUnavailableError('Convex embed key validation unavailable: invalid-payload');View on GitHub (pinned to 7d06c8633d)