mastra-ai/mastra · warning

License server responded with ${response.status}

Error message

License server responded with ${response.status}

What it means

During license validation, performValidation retries transient server conditions. If the license server ultimately responds with HTTP 429 or any 5xx after retries are exhausted, it throws 'License server responded with <status>'. The comment in source makes the intent explicit: this is treated like an unreachable server, so lease/grace semantics apply instead of invalidating the key.

Source

Thrown at packages/core/src/license/index.ts:168

    try {
      if (!this.licenseUrl?.startsWith('https://') && !this.licenseUrl?.includes('localhost')) {
        this.logger?.warn('License URL is not HTTPS. Proceeding, but this is insecure.');
      }

      const response = await this.fetchWithRetry(`${this.licenseUrl}/validate`, {
        method: 'POST',
        headers: {
          'content-type': 'application/json',
        },
        body: JSON.stringify({ licenseKey: this.licenseKey }),
      });

      // A 429 or 5xx that survived the retries is a transient server
      // condition, not a verdict on the license — treat it like an
      // unreachable server so the lease/grace semantics below apply
      // instead of invalidating the key.
      if (response.status === 429 || response.status >= 500) {
        throw new Error(`License server responded with ${response.status}`);
      }

      const data = (await response.json()) as LicenseValidationResponse;

      if (data.valid) {
        this.status = 'valid';
        this.logger?.info(`License validated${data.expiresAt ? `, expires ${data.expiresAt.slice(0, 10)}` : ''}`);
        this.cachedResult = data;

        const ttlSeconds = data.leaseTtlSeconds || this.DEFAULT_TTL_MS / 1000;
        this.cacheExpiry = now + ttlSeconds * 1000;
        this.gracePeriodEnd = now + this.GRACE_PERIOD_MS;

        this.scheduleRevalidation(ttlSeconds);
        return true;
      } else if (data.code === 'RATE_LIMITED') {
        // Defensive: a throttle marker in the body without a 429 status is
        // still transient, not a license verdict.

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Retry validation after a delay — your app should keep operating under lease/grace while the server is unavailable
  2. Check the license service status page / network path (proxy, firewall, DNS) for the failing 5xx
  3. Reduce concurrent validators: consolidate license checks across processes or use a cached lease
  4. If 429, back off and slow the validation cadence; spread CI jobs' validation attempts

Example fix

// before
await license.performValidation(); // throws on 429/5xx
// after
try {
  await license.performValidation();
} catch {
  // transient server condition — grace lease still applies
  await new Promise(r => setTimeout(r, 5000));
  await license.performValidation();
}
Defensive patterns

Strategy: retry

Try / catch

try {
  await licenseClient.validate();
} catch (e) {
  const m = /License server responded with (\d+)/.exec(e.message);
  if (m && (m[1] === '429' || m[1].startsWith('5'))) {
    await new Promise(r => setTimeout(r, 2000));
    await licenseClient.validate(); // grace/lease still applies
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: License server returns 429 (rate limited) or 5xx (server error) on a validation attempt that survived all retries — network/server outage, license-service deployment, or client exceeding request limits across many processes.

Common situations: Mastra license service outage or degraded performance; many CI runners validating one key concurrently; firewall/proxy returning 5xx for the license endpoint; transient cloud incident during app startup.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/302e1248f5f24cff. Report an issue: GitHub.