mastra-ai/mastra · error

Request failed (${res.status}) / server-provided message

Error message

Request failed (${res.status}) / server-provided message

What it means

throwRequestError in audit.ts converts a non-OK HTTP response into an Error. It prefers a server-provided message or error string from the JSON body and otherwise falls back to 'Request failed (<status>).'. fetchAuditEvents and fetchAuditPortalLink delegate all HTTP failures to it.

Source

Thrown at mastracode/factory-ui/src/ui/domains/factory/services/audit.ts:102

    'actors' in value &&
    typeof value.actors === 'object' &&
    value.actors !== null &&
    !Array.isArray(value.actors) &&
    Object.values(value.actors).every(isAuditActorProfile) &&
    (!('nextCursor' in value) || isOptionalString(value.nextCursor))
  );
}

async function throwRequestError(res: Response): Promise<never> {
  let message = `Request failed (${res.status})`;
  try {
    const body: unknown = await res.json();
    if (typeof body === 'object' && body !== null) {
      if ('message' in body && typeof body.message === 'string') message = body.message;
      else if ('error' in body && typeof body.error === 'string') message = body.error;
    }
  } catch {}
  throw new Error(message);
}

export async function fetchAuditEvents(
  baseUrl: string,
  factoryProjectId: string,
  options: { actions?: string[]; actorIds?: string[]; before?: string; limit?: number; signal?: AbortSignal } = {},
): Promise<AuditEventPage> {
  const query = new URLSearchParams();
  if (options.actions && options.actions.length > 0) query.set('actions', options.actions.join(','));
  if (options.actorIds && options.actorIds.length > 0) query.set('actorIds', options.actorIds.join(','));
  if (options.before) query.set('before', options.before);
  if (options.limit) query.set('limit', String(options.limit));
  const qs = query.size > 0 ? `?${query}` : '';
  const res = await fetch(`${baseUrl}/web/factory/projects/${encodeURIComponent(factoryProjectId)}/audit${qs}`, {
    headers: { Accept: 'application/json' },
    credentials: 'include',
    signal: options.signal,
  });

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Read the thrown message: a server body message names the real problem; 'Request failed (N)' means inspect status N.
  2. For 401, re-authenticate; for 403, grant the actor audit read permission.
  3. For 5xx/429, retry with backoff and check server/gateway logs.
  4. Confirm the baseUrl points at the API returning JSON, not an HTML-serving proxy.

Example fix

// before
class AuditError extends Error { constructor(public status: number, m: string){super(m);} }
// after
class AuditError extends Error {
  constructor(public status: number, m: string){ super(m); }
}
throw new AuditError(res.status, message); // callers can branch on status, e.g. 401 -> re-login
Defensive patterns

Strategy: try-catch

Type guard

function isAuditHttpError(e: unknown): e is Error & { status?: number } {
  return e instanceof Error && typeof (e as any).status === 'number';
}

Try / catch

try {
  const events = await fetchAuditEvents(baseUrl, projectId, { actions, actorIds });
} catch (e) {
  const status = (e as { status?: number }).status;
  if (status === 401) redirectToLogin();
  else if (status === 403) showNoPermission();
  else showToast(e instanceof Error ? e.message : 'Failed to load audit events');
}

Prevention

When it happens

Trigger: Any non-ok response from the audit endpoints — 401/403 unauthenticated or unauthorized user, 404 unknown project, 429 rate limit, 5xx server error — where the body either carries {message}|{error} or is non-JSON (triggering the fallback text).

Common situations: Session cookie expired during an audit-log page load; user lacks audit-view permission; HTML login page returned by an auth gateway (hence the fallback); transient 502 from the API.

Related errors


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