mastra-ai/mastra · error
HttpRemoteStrategy: requestContext is not JSON-serializable.
Error message
HttpRemoteStrategy: requestContext is not JSON-serializable. ${err instanceof Error ? err.message : String(err)} What it means
Before POSTing step params, #buildBody round-trips requestContext through JSON.parse(JSON.stringify(...)) to guarantee a plain serializable payload. If requestContext contains non-JSON values (functions, class instances with circular refs, BigInt, undefined in hostile spots), serialization throws and this descriptive error replaces the opaque JSON error.
Source
Thrown at packages/core/src/worker/strategies/http-remote-strategy.ts:99
return res.json() as Promise<StepResult<unknown, unknown, unknown, unknown>>;
}
/**
* Build a JSON-serializable request body. The `params.requestContext` is
* a plain object; if a caller stuffed a non-serializable value into it we
* surface a clear error instead of silently dropping fields.
*
* `abortSignal` is consumed via fetch's `signal` argument — it must not
* be in the body.
*/
#buildBody(params: StepExecutionParams): string {
const { abortSignal: _abortSignal, requestContext, ...rest } = params;
let safeRequestContext: Record<string, unknown>;
try {
safeRequestContext = JSON.parse(JSON.stringify(requestContext ?? {}));
} catch (err) {
throw new Error(
`HttpRemoteStrategy: requestContext is not JSON-serializable. ${err instanceof Error ? err.message : String(err)}`,
);
}
return JSON.stringify({
...rest,
requestContext: safeRequestContext,
});
}
#combineSignals(externalSignal?: AbortSignal): AbortSignal {
const timeoutSignal = AbortSignal.timeout(this.#timeoutMs);
if (!externalSignal) return timeoutSignal;
// AbortSignal.any aborts when any input aborts.
if (typeof AbortSignal.any === 'function') {
return AbortSignal.any([timeoutSignal, externalSignal]);
}
// Fallback for runtimes without AbortSignal.anyView on GitHub (pinned to 75dd419e61)
Solutions
- Ensure requestContext holds only JSON-safe values (strings, numbers, plain objects, arrays)
- Remove non-serializable members (clients, loggers, functions) before invoking the step
- Convert special types explicitly (BigInt -> string, Date -> ISO string, Map -> Object.fromEntries)
Example fix
// before
requestContext.set('db', prisma);
// after
requestContext.set('dbConnectionString', process.env.DATABASE_URL); Defensive patterns
Strategy: type-guard
Validate before calling
function isJsonSafe(v: unknown, seen = new Set()): boolean {
if (v === null || typeof v !== 'object') return typeof v !== 'function' && typeof v !== 'bigint';
if (seen.has(v)) return false;
seen.add(v);
return Object.values(v).every((x) => isJsonSafe(x, seen));
} Try / catch
try {
await strategy.executeStep(params);
} catch (e) {
if (e instanceof Error && e.message.includes('not JSON-serializable')) {
console.error('requestContext contains non-JSON values:', e.message);
}
throw e;
} Prevention
- Keep requestContext limited to plain JSON data
- Never place clients, loggers, or request objects in requestContext
- Convert Date/Map/BigInt values explicitly before passing them
- Test with InProcessStrategy early, but validate serializability before HTTP execution
When it happens
Trigger: Storing a function, class instance, Map/Set, circular object graph, or BigInt in requestContext before executing a step via HttpRemoteStrategy.
Common situations: Passing a logger, DB client, or Prisma client via requestContext; putting the raw Hono/Express request object into context; using context values that worked locally with InProcessStrategy but break over HTTP.
Related errors
- StepExecutionError(res.status, text)
- Slack OAuth HTTP error: ${tokenResponse.status} ${tokenRespo
- Query parameter "status" must be "draft" or "published"
- Agent ${agentId} not found
- Path must include :agentId to route to the correct agent or
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/8f681fe6ea2c804d.
Report an issue: GitHub.