koala73/worldmonitor · error · EmbedKeyUnavailableError
Convex embed key validation unavailable: invalid-json
Error message
Convex embed key validation unavailable: invalid-json
What it means
fetchFromConvex fetches embed-key validation data from a Convex endpoint and parses the HTTP response body as JSON. If resp.json() throws (body is not valid JSON — empty, HTML error page, truncated, or wrong content type), the function converts that low-level failure into EmbedKeyUnavailableError with reason 'invalid-json'. It signals the upstream Convex validation source is unreachable or malformed rather than that the key itself is invalid.
Solutions
- Log the raw response status and body text before json() to identify what the Convex endpoint actually returned
- Verify the Convex deployment URL / environment config is correct and the backend is deployed and healthy
- Retry with backoff — transient gateway errors commonly produce HTML bodies
- Add a content-type check (application/json) and fail fast with a clearer message if it is not JSON
- Treat EmbedKeyUnavailableError as 'validation unavailable' in callers and fall back to a cached or secondary validation path
Example fix
// before
value = await resp.json();
// after
const raw = await resp.text();
try { value = JSON.parse(raw); }
catch { throw new EmbedKeyUnavailableError(`Convex embed key validation unavailable: invalid-json (status=${resp.status}, body=${raw.slice(0, 120)})`); } Defensive patterns
Strategy: try-catch
Validate before calling
// Before the call: verify config
if (!process.env.CONVEX_URL || !/^https:\/\/.+\.convex\.(site|cloud)/.test(process.env.CONVEX_URL)) {
throw new Error('CONVEX_URL is not a valid Convex deployment URL');
} Type guard
function isJsonObject(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v);
} Try / catch
try {
const key = await fetchFromConvex(hash);
} catch (err) {
if (err instanceof EmbedKeyUnavailableError && err.message.includes('invalid-json')) {
// validation source unreachable: fall back to cached/secondary validation
logger.warn({ hash, err }, 'convex embed-key source returned non-JSON; using fallback');
return fallbackValidation(hash);
}
throw err;
} Prevention
- Pin and health-check the Convex deployment URL; alert on non-2xx or HTML responses
- Check resp.headers content-type === 'application/json' before parsing
- Add retry with exponential backoff for transient gateway/5xx responses
- Log raw body prefix on parse failure for diagnosis
When it happens
Trigger: The Convex HTTP endpoint returns a non-JSON body: a 502/504 HTML gateway error page, an empty body, a plain-text error, or a gzip/encoding mismatch that corrupts the body before parsing.
Common situations: Convex deployment URL misconfigured (pointing at a non-Convex host), Convex backend outage or deploy in progress, a proxy/CDN intercepting the request and returning an HTML error page, or network truncation.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
- bodyTransportFailure ? 'NHC_POINT_REQUEST_FAILED' : 'NHC_POINT_RESPONSE_INVALID'
- Convex embed key validation unavailable: fetch-error
- relay returned ${resp.status}
- Exa returned malformed structured summary for ${url}
- PRO_REQUIRED
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/1347b4d8191faa7f.
Report an issue: GitHub.
Appendix: source
Thrown at server/_shared/embed-key.ts:168
},
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');
}
return value;
}
/**
* Delete the Redis cache entry for a specific embed key hash.
* Called after revocation so the key cannot be used during the TTL window.
* Uses prefixed keys (no raw=true) matching the cache writes above.
*/
export async function invalidateEmbedKeyCache(keyHash: string): Promise<void> {
await deleteRedisKey(`${CACHE_KEY_PREFIX}${keyHash}`);
}
View on GitHub (pinned to 7d06c8633d)