mastra-ai/mastra · error

MastraFactory: 'sandbox' is now a callback, not an options o

Error message

MastraFactory: 'sandbox' is now a callback, not an options object. It receives a FactorySandboxContext and returns a MastraSandbox, so the host chooses the provider per session:
  sandbox: ctx => new E2BSandbox({ id: ctx.sessionId })
The old options map three ways: 'machine' becomes the provider instance you construct inside the callback (one per session instead of one cloned template); 'workdir' is gone — remote providers clone into the VM's home directory and local providers check out under their own workingDirectory; 'maxSandboxes' is gone with the sandbox fleet — there is one sandbox per session and no pool to cap. Omit 'sandbox' entirely to disable sandboxes.

What it means

The factory's 'sandbox' option was redesigned: it is now a callback receiving a FactorySandboxContext and returning a MastraSandbox, letting the host choose the provider per session. Passing a plain object (the legacy fleet-style options with 'machine', 'workdir', 'maxSandboxes') throws this migration-targeted message explaining how each old option maps to the new model. Omitting 'sandbox' entirely disables sandboxes.

Source

Thrown at mastracode/factory/src/factory.ts:428

      comments: workItemCommentsStorage,
      workItems: workItemsStorage,
      projects: factoryProjectsStorage,
      channelIdentity: channelIdentityStorage,
      audit: auditDomain,
    });

    // The sandbox config is a bare callback constructing a session's sandbox
    // from intent. Shape-only validation: probing it with a synthetic ctx at
    // boot would construct against a fake session, so only the type is
    // checked.
    const sandboxConfig = this.#config.sandbox;
    if (sandboxConfig !== undefined && typeof sandboxConfig !== 'function') {
      // An object here is almost certainly the pre-callback config, which
      // described a fleet the factory managed itself. That fleet is gone:
      // sandboxes are per session and the host constructs them, so say what to
      // write instead rather than only naming the expected type.
      if (typeof sandboxConfig === 'object' && sandboxConfig !== null) {
        throw new Error(
          `MastraFactory: 'sandbox' is now a callback, not an options object. It receives a FactorySandboxContext and returns a MastraSandbox, so the host chooses the provider per session:\n` +
            `  sandbox: ctx => new E2BSandbox({ id: ctx.sessionId })\n` +
            `The old options map three ways: 'machine' becomes the provider instance you construct inside the callback (one per session instead of one cloned template); 'workdir' is gone — remote providers clone into the VM's home directory and local providers check out under their own workingDirectory; 'maxSandboxes' is gone with the sandbox fleet — there is one sandbox per session and no pool to cap. Omit 'sandbox' entirely to disable sandboxes.`,
        );
      }
      throw new Error(
        `MastraFactory: 'sandbox' must be a function constructing a MastraSandbox from a FactorySandboxContext.`,
      );
    }

    const workspaceRegistry = new FactoryWorkspaceRegistry();

    // One shared OAuth state signer per boot. The deploy entry supplies a
    // replica-stable secret when needed; otherwise local development gets a
    // per-process random signer (`stable: false`).
    const stateSigner = createStateSigner(this.#config.stateSecret);

    // One-time provider initialization with factory-level context (e.g.

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Replace the options object with a callback that constructs a provider per session: sandbox: ctx => new E2BSandbox({ id: ctx.sessionId }).
  2. If you used 'machine', construct the provider instance yourself inside the callback (one per session, no cloned template).
  3. Drop 'workdir' — remote providers clone into the VM home dir; local providers manage their own workingDirectory.
  4. Drop 'maxSandboxes' — there is one sandbox per session and no pool to cap.
  5. If sandboxes are not needed, remove the sandbox key entirely to disable them.

Example fix

// before
sandbox: { machine: 'base', workdir: '/tmp/sb', maxSandboxes: 10 }

// after
import { E2BSandbox } from '@mastra/e2b';
sandbox: ctx => new E2BSandbox({ id: ctx.sessionId })
Defensive patterns

Strategy: type-guard

Validate before calling

import { z } from 'zod';
const sandboxSchema = z.function();
if (config.sandbox !== undefined && !sandboxSchema.safeParse(config.sandbox).success) {
  throw new Error("config.sandbox must be a callback: ctx => new E2BSandbox({ id: ctx.sessionId })");
}

Type guard

function isSandboxCallback(v) {
  return v === undefined || (typeof v === 'function' && v.length <= 1);
}
if (!isSandboxCallback(config.sandbox)) throw new Error('sandbox must be a FactorySandboxContext callback');

Try / catch

try {
  const factory = new MastraFactory(config);
  await factory.prepare();
} catch (err) {
  if (err.message.includes("'sandbox' is now a callback")) {
    throw new Error('Migration needed: sandbox config uses the removed fleet options object', { cause: err });
  }
  throw err;
}

Prevention

When it happens

Trigger: Setting sandbox in the factory config to a non-null object instead of a function, e.g. sandbox: { machine: {...}, workdir: '/tmp/sb', maxSandboxes: 10 } — the old fleet configuration shape from before the callback API.

Common situations: Upgrading from a previous factory version that accepted a fleet options object; copying config from old docs or an example repo; type assertions (as any) hiding the type error that would otherwise catch the object at compile time.

Related errors


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