{"record":{"id":"b7cf8b7771b0213a","repo":"mastra-ai/mastra","slug":"mastrafactory-prepare-called-twice","errorCode":null,"errorMessage":"MastraFactory.prepare() called twice","messagePattern":"MastraFactory\\.prepare\\(\\) called twice","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/factory.ts","lineNumber":310,"sourceCode":"      throw new Error(\n        \"MastraFactory: 'storage' is required. Pass a FactoryStorage backend — e.g. \" +\n          \"new PgFactoryStorage({ connectionString }) from '@mastra/pg' for deployments, or \" +\n          \"new LibSQLFactoryStorage({ url }) from '@mastra/libsql' for local dev.\",\n      );\n    }\n    this.#config = config;\n  }\n\n  /**\n   * Resolve feature readiness, wire every dependency explicitly, and assemble\n   * everything needed to construct the server-owned Mastra. Returns the args\n   * for the `new Mastra(...)` literal that must live in the entry file.\n   */\n  async prepare(): Promise<MastraArgs> {\n    // Guard set synchronously (before the first await) so overlapping calls —\n    // not just strictly sequential ones — can't double-seed the runtime\n    // registry or double-run one-time adapter init.\n    if (this.#preparing) throw new Error('MastraFactory.prepare() called twice');\n    this.#preparing = true;\n\n    const publicOrigin = (this.#config.publicUrl ?? 'http://localhost:4111').replace(/\\/+$/, '');\n    const allowedOrigins = (this.#config.allowedOrigins ?? []).map(o => o.replace(/\\/+$/, '')).filter(Boolean);\n    const storage = this.#config.storage;\n    const vector = this.#config.vector;\n    const pubsub = this.#config.pubsub;\n    // Default auth: honor an explicitly-passed provider (including `null` to\n    // disable auth) as-is; otherwise fall back to `MastraAuthStudio`\n    // (platform-proxied identity). The default derives its cookie domain\n    // from `publicUrl` — deploys on `<sub>.mastra.cloud` mint parent-domain\n    // cookies without the caller wiring `MASTRA_COOKIE_DOMAIN` explicitly.\n    const configuredAuth = this.#config.auth;\n    const auth: IMastraAuthProvider | undefined =\n      configuredAuth === null ? undefined : (configuredAuth ?? buildDefaultStudioAuth(publicOrigin));\n    if (auth && !this.#config.secretEncryption) {\n      console.warn(\n        \"[factory] auth is enabled but 'secretEncryption' is not configured. Persisted model credentials, \" +","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/factory.ts#L292-L328","documentation":"prepare() performs one-time initialization: it seeds the runtime registry and runs one-time adapter init, producing the MastraArgs for the entry file's new Mastra(...) call. A synchronous #preparing guard is set before the first await so overlapping calls — not just sequential ones — cannot double-seed state. Calling prepare() a second time, while a call is in flight or after completion, throws this error.","triggerScenarios":"Calling factory.prepare() more than once: e.g. invoking it both in an entry file and in a helper like prepareFactory, calling it again after an await, or racing two concurrent prepare() calls (the guard is set synchronously before the first await, so overlapping calls are also rejected).","commonSituations":"Dev-server hot reload re-executing an init module; accidentally awaiting prepare() in two startup paths (e.g. both a top-level init and a route handler); wrapping prepare() in retry logic that re-invokes it after a partial failure.","solutions":["Call prepare() exactly once per process and reuse the returned MastraArgs; cache the promise if multiple callers need it (const argsPromise = factory.prepare()).","Refactor so only a single startup entry point invokes prepare(); other code should receive the cached MastraArgs.","Guard call sites with a module-level singleton: export const prepared = prepareOnce() where prepareOnce memoizes the promise.","Restart the process instead of re-calling prepare() after a failure; the #preparing flag is not reset.","Check duplicated wrappers such as prepareFactory or init helpers that each call prepare() internally."],"exampleFix":"// before\nconst args1 = await factory.prepare();\nconst args2 = await factory.prepare(); // throws\n\n// after\nconst args = await factory.prepare(); // once, shared\nexport const mastra = new Mastra(args);","handlingStrategy":"fallback","validationCode":"// memoize the prepare call so it can never run twice\nlet preparePromise = null;\nexport function prepareOnce(factory) {\n  preparePromise ??= factory.prepare();\n  return preparePromise;\n}","typeGuard":"function isPrepared(factory) {\n  return factory != null && typeof factory.prepare === 'function';\n}\n// then guard usage: only call prepare() when no cached MastraArgs exists","tryCatchPattern":"let args;\ntry {\n  args = await prepareOnce(factory);\n} catch (err) {\n  if (err.message.includes('prepare() called twice')) {\n    // a concurrent call already ran prepare; await the shared promise\n    args = await preparePromise;\n  } else throw err;\n}","preventionTips":["Expose a single module-level prepareOnce() and never call factory.prepare() directly elsewhere","Cache the returned MastraArgs and pass them to every consumer","Never put prepare() inside retry loops or hot-reload re-executed code","Have secondary entry points (tests, scripts) import the shared prepared instance"],"tags":["lifecycle","double-initialization","concurrency"],"backgroundTag":"double-initialization","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}