{"record":{"id":"3a805f1bd96820d9","repo":"mastra-ai/mastra","slug":"mastrafactory-sandbox-is-now-a-callback-not-an","errorCode":null,"errorMessage":"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 })\nThe 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.","messagePattern":"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 \\}\\)\nThe 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\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/factory.ts","lineNumber":428,"sourceCode":"      comments: workItemCommentsStorage,\n      workItems: workItemsStorage,\n      projects: factoryProjectsStorage,\n      channelIdentity: channelIdentityStorage,\n      audit: auditDomain,\n    });\n\n    // The sandbox config is a bare callback constructing a session's sandbox\n    // from intent. Shape-only validation: probing it with a synthetic ctx at\n    // boot would construct against a fake session, so only the type is\n    // checked.\n    const sandboxConfig = this.#config.sandbox;\n    if (sandboxConfig !== undefined && typeof sandboxConfig !== 'function') {\n      // An object here is almost certainly the pre-callback config, which\n      // described a fleet the factory managed itself. That fleet is gone:\n      // sandboxes are per session and the host constructs them, so say what to\n      // write instead rather than only naming the expected type.\n      if (typeof sandboxConfig === 'object' && sandboxConfig !== null) {\n        throw new Error(\n          `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` +\n            `  sandbox: ctx => new E2BSandbox({ id: ctx.sessionId })\\n` +\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.`,\n        );\n      }\n      throw new Error(\n        `MastraFactory: 'sandbox' must be a function constructing a MastraSandbox from a FactorySandboxContext.`,\n      );\n    }\n\n    const workspaceRegistry = new FactoryWorkspaceRegistry();\n\n    // One shared OAuth state signer per boot. The deploy entry supplies a\n    // replica-stable secret when needed; otherwise local development gets a\n    // per-process random signer (`stable: false`).\n    const stateSigner = createStateSigner(this.#config.stateSecret);\n\n    // One-time provider initialization with factory-level context (e.g.","sourceCodeStart":410,"sourceCodeEnd":446,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/factory.ts#L410-L446","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Replace the options object with a callback that constructs a provider per session: sandbox: ctx => new E2BSandbox({ id: ctx.sessionId }).","If you used 'machine', construct the provider instance yourself inside the callback (one per session, no cloned template).","Drop 'workdir' — remote providers clone into the VM home dir; local providers manage their own workingDirectory.","Drop 'maxSandboxes' — there is one sandbox per session and no pool to cap.","If sandboxes are not needed, remove the sandbox key entirely to disable them."],"exampleFix":"// before\nsandbox: { machine: 'base', workdir: '/tmp/sb', maxSandboxes: 10 }\n\n// after\nimport { E2BSandbox } from '@mastra/e2b';\nsandbox: ctx => new E2BSandbox({ id: ctx.sessionId })","handlingStrategy":"type-guard","validationCode":"import { z } from 'zod';\nconst sandboxSchema = z.function();\nif (config.sandbox !== undefined && !sandboxSchema.safeParse(config.sandbox).success) {\n  throw new Error(\"config.sandbox must be a callback: ctx => new E2BSandbox({ id: ctx.sessionId })\");\n}","typeGuard":"function isSandboxCallback(v) {\n  return v === undefined || (typeof v === 'function' && v.length <= 1);\n}\nif (!isSandboxCallback(config.sandbox)) throw new Error('sandbox must be a FactorySandboxContext callback');","tryCatchPattern":"try {\n  const factory = new MastraFactory(config);\n  await factory.prepare();\n} catch (err) {\n  if (err.message.includes(\"'sandbox' is now a callback\")) {\n    throw new Error('Migration needed: sandbox config uses the removed fleet options object', { cause: err });\n  }\n  throw err;\n}","preventionTips":["Update config types to the callback signature so legacy objects fail typecheck","Search the codebase for 'maxSandboxes'/'workdir' to find leftover legacy config","Copy the callback pattern from current docs: ctx => new E2BSandbox({ id: ctx.sessionId })","Add a startup test that runs prepare() with the real config"],"tags":["configuration","breaking-change","sandbox","migration"],"backgroundTag":"deprecated-api-shape","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}