{"record":{"id":"7847e67507c5574e","repo":"mastra-ai/mastra","slug":"mastrafactory-sandbox-must-be-a-function-constr","errorCode":null,"errorMessage":"MastraFactory: 'sandbox' must be a function constructing a MastraSandbox from a FactorySandboxContext.","messagePattern":"MastraFactory: 'sandbox' must be a function constructing a MastraSandbox from a FactorySandboxContext\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/factory.ts","lineNumber":434,"sourceCode":"\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.\n    // better-auth builds its default instance on the backend's auth\n    // database, WorkOS derives its redirect URI from the public URL).\n    // Failures surface here, at prepare() — a misconfigured provider must\n    // not boot.\n    if (auth && hasAuthInit(auth)) {\n      await timedPhase('prepare.auth.init', () =>","sourceCodeStart":416,"sourceCodeEnd":452,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/factory.ts#L416-L452","documentation":"After the sandbox fleet removal, 'sandbox' must be a function that takes a FactorySandboxContext and constructs a MastraSandbox. Any value that is defined but not a function (e.g. a string, number, boolean, or array) throws this generic type-validation error. Non-function, non-object values do not get the detailed migration message — that is reserved for legacy objects (error 313).","triggerScenarios":"Passing sandbox: true, a provider class without new (sandbox: E2BSandbox), a string, or any other non-function value in the factory config; also passing a Promise or a constructed-but-non-MastraSandbox value that is not a function.","commonSituations":"Forgetting the arrow wrapper and assigning the constructor/class itself; typo'd config where sandbox gets a boolean flag; JSON-loaded config where functions cannot be expressed.","solutions":["Wrap the construction in a function: sandbox: ctx => new E2BSandbox({ id: ctx.sessionId }).","If you intended to disable sandboxes, delete the sandbox key instead of passing a falsy placeholder.","Remove any 'as any' casts so TypeScript flags the non-function type at compile time.","Ensure the callback returns a MastraSandbox instance, not a Promise or class reference."],"exampleFix":"// before\nsandbox: E2BSandbox\n\n// after\nsandbox: ctx => new E2BSandbox({ id: ctx.sessionId })","handlingStrategy":"type-guard","validationCode":"if (config.sandbox !== undefined && typeof config.sandbox !== 'function') {\n  throw new TypeError('sandbox must be a function: ctx => new E2BSandbox({ id: ctx.sessionId })');\n}","typeGuard":"function isSandboxFactory(v) {\n  return typeof v === 'function' && v.prototype?.execute !== undefined || typeof v === 'function';\n}\n// stricter: require it to accept a context and be a declared arrow/constructor\nif (config.sandbox !== undefined && typeof config.sandbox !== 'function') throw new TypeError('sandbox must be a callback');","tryCatchPattern":"try {\n  await factory.prepare();\n} catch (err) {\n  if (err.message.includes(\"'sandbox' must be a function\")) {\n    throw new Error('Config error: sandbox must be a callback returning a MastraSandbox', { cause: err });\n  }\n  throw err;\n}","preventionTips":["Never assign a class/constructor directly; wrap with ctx => new Provider({...})","Type the config field as (ctx: FactorySandboxContext) => MastraSandbox so non-functions fail compile","Don't load function-valued config from JSON; keep it in code","Remove the sandbox key entirely when sandboxes are disabled"],"tags":["configuration","type-error","sandbox"],"backgroundTag":"invalid-config-type","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}