mastra-ai/mastra · error · HTTPException

apiError.message || defaultMessage

Error message

apiError.message || defaultMessage

What it means

handleError normalizes any thrown value in a server handler into an HTTPException. It casts the error to ApiError and uses its message, falling back to the defaultMessage passed by the caller; the resulting message shown is apiError.message || defaultMessage with status defaulting to 500. This is the deployer server's consistent error translation layer.

Source

Thrown at packages/deployer/src/server/handlers/error.ts:10

import type { Context } from 'hono';
import { HTTPException } from 'hono/http-exception';
import type { ContentfulStatusCode } from 'hono/utils/http-status';

import type { ApiError } from '../types';

// Helper to handle errors consistently
export function handleError(error: unknown, defaultMessage: string): Promise<Response> {
  const apiError = error as ApiError;
  throw new HTTPException((apiError.status || 500) as ContentfulStatusCode, {
    message: apiError.message || defaultMessage,
    cause: apiError.cause,
  });
}
export function errorHandler(err: Error, c: Context, isDev?: boolean): Response {
  if (err instanceof HTTPException) {
    if (isDev) {
      return c.json({ error: err.message, cause: err.cause, stack: err.stack }, err.status);
    }
    return c.json({ error: err.message }, err.status);
  }

  c.get('mastra').getLogger().error(err);
  return c.json({ error: 'Internal Server Error' }, 500);
}

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Inspect the response body message: if it is the defaultMessage, the original error had no message — check server logs for the real cause.
  2. For restart endpoints, verify the workflow run id exists and storage is reachable before retrying.
  3. Throw typed ApiError-shaped errors (with status and message) from custom handlers to control the returned status code.

Example fix

// before
throw new Error(); // no message -> 500 + defaultMessage
// after
throw Object.assign(new Error('Workflow run not found'), { status: 404 });
Defensive patterns

Strategy: type-guard

Type guard

function isApiError(e: unknown): e is { status?: number; message?: string; cause?: unknown } {
  return typeof e === 'object' && e !== null && ('status' in e || 'message' in e);
}

Try / catch

try {
  const res = await fetch('/api/workflows/runs/restart', { method: 'POST', body });
  if (!res.ok) {
    const { message } = await res.json();
    throw new Error(message || `Request failed with ${res.status}`);
  }
} catch (e) {
  if (e instanceof HTTPException) {
    console.error(`Server error ${e.status}: ${e.message}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Any handler (e.g. restartAllActiveWorkflowRunsHandler) throws a non-HTTP error, or throws an object without a message/status; handleError converts it — with status undefined becoming 500 and message undefined becoming the handler's defaultMessage.

Common situations: Workflow restart fails because the workflow run no longer exists in storage; storage/connection errors inside handlers; an error thrown without a message property reaching the API surface as a 500.

Related errors


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