koala73/worldmonitor · error · EmbedKeyUnavailableError

Convex embed key validation unavailable: invalid-payload

Error message

Convex embed key validation unavailable: invalid-payload

What it means

After successfully parsing the Convex response body as JSON, fetchFromConvex validates its shape with isEmbedKeyResult. If the parsed value is a JSON document but does not match the expected embed-key result schema, this EmbedKeyUnavailableError with reason 'invalid-payload' is thrown. It means the Convex function responded but returned an unexpected structure — usually a contract drift between the Convex query and this client.

Solutions

  1. Inspect the actual payload from the Convex endpoint and diff it against isEmbedKeyResult's expected fields
  2. Ensure the Convex query and the server/_shared/embed-key.ts contract are deployed in lockstep
  3. Verify the Convex deployment URL points at the intended environment (staging vs production)
  4. Check for an auth or proxy layer returning its own JSON error body with HTTP 200
  5. Broaden the error message to include the unexpected keys for faster diagnosis

Example fix

// before
if (!isEmbedKeyResult(value)) {
  throw new EmbedKeyUnavailableError('Convex embed key validation unavailable: invalid-payload');
}
// after
if (!isEmbedKeyResult(value)) {
  throw new EmbedKeyUnavailableError(`Convex embed key validation unavailable: invalid-payload ${JSON.stringify(value).slice(0, 200)}`);
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Before the call: assert deployment/contract version
const res = await fetch(`${CONVEX_URL}/api/query`, { method: 'POST' });
if (res.status !== 200) throw new Error(`Convex returned ${res.status}; deploy is unhealthy`);

Type guard

function isEmbedKeyResult(v: unknown): v is { key: string; valid: boolean } {
  return typeof v === 'object' && v !== null
    && 'key' in v && 'valid' in v
    && typeof (v as any).key === 'string'
    && typeof (v as any).valid === 'boolean';
}

Try / catch

try {
  const result = await fetchFromConvex(hash);
} catch (err) {
  if (err instanceof EmbedKeyUnavailableError && err.message.includes('invalid-payload')) {
    logger.error({ hash, err }, 'Convex payload contract drift; check deploy versions');
    return null; // or fall back to local validation
  }
  throw err;
}

Prevention

When it happens

Trigger: The Convex query was changed or renamed and now returns a different object shape; a deploy returned an error object like {error: ...} with HTTP 200; a proxy returned JSON (e.g. {message:'unauthorized'}) instead of the expected result; the Convex function returns null wrapped differently or omits required fields.

Common situations: Version skew after a Convex deploy (server updated, API worker not, or vice versa); environment pointing at the wrong Convex deployment (staging vs production schema); auth/proxy layer injecting its own JSON error body.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/e19dee95b5a12ec2. Report an issue: GitHub.

Appendix: source

Thrown at server/_shared/embed-key.ts:173

    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)