mastra-ai/mastra · error
MastraFactory: integration '${integration.id}' signs OAuth s
Error message
MastraFactory: integration '${integration.id}' signs OAuth state and requires a replica-stable state secret, but none is configured. Set 'stateSecret' on the factory config. What it means
Integrations that sign OAuth 'state' parameters require a replica-stable secret: a per-process random signer breaks the OAuth callback on any replica that did not sign the state. During prepare(), if an active integration sets requiresStableStateSigner and the configured state signer is not stable (no stateSecret configured), the factory fails at boot rather than at first OAuth flow.
Source
Thrown at mastracode/factory/src/factory.ts:603
),
}
: {}),
messageReader: {
listMessages: async input => {
const memory = await storage.getMastraStorage().getStore('memory');
return memory ? memory.listMessages(input) : { messages: [], hasMore: false };
},
},
})
: undefined;
// Boot assertion: an active integration that signs OAuth `state` needs a
// replica-stable signer — a per-process random secret silently breaks the
// OAuth callback on any replica that didn't sign the state. Fail loud now
// instead. (The built-ins also assert this inside their readiness gates.)
for (const { integration } of integrationRegistrations) {
if (integration.requiresStableStateSigner && !stateSigner.stable) {
throw new Error(
`MastraFactory: integration '${integration.id}' signs OAuth state and requires a ` +
`replica-stable state secret, but none is configured. Set 'stateSecret' on the factory config.`,
);
}
}
// The SDK needs to know which backend the injected Mastra store uses
// (its own `instanceof` detection breaks when the dependency graph holds
// duplicate package copies). Resolve it by walking the FactoryStorage
// prototype chain by class name — the factory can't import the concrete
// classes since '@mastra/pg' / '@mastra/libsql' are the user's choice.
const mastraStorageBackend = (() => {
for (let proto = Object.getPrototypeOf(storage); proto; proto = Object.getPrototypeOf(proto)) {
if (proto.constructor?.name === 'PgFactoryStorage') return 'pg' as const;
if (proto.constructor?.name === 'LibSQLFactoryStorage') return 'libsql' as const;
}
return undefined;
})();View on GitHub (pinned to 75dd419e61)
Solutions
- Set stateSecret in the factory config: stateSecret: process.env.FACTORY_STATE_SECRET, sourced from a stable secret store.
- Provision the same secret across all replicas/instances so any replica can verify state signed by another.
- Add the env var to your deployment platform's secret manager and restart all instances after adding it.
- For local dev, generate one long random value and commit it to .env (gitignored) so dev behavior matches production.
Example fix
// before
const factory = new MastraFactory({
storage,
integrations: [new GithubIntegration()],
});
// after
const factory = new MastraFactory({
storage,
stateSecret: process.env.FACTORY_STATE_SECRET,
integrations: [new GithubIntegration()],
}); Defensive patterns
Strategy: validation
Validate before calling
if (!process.env.FACTORY_STATE_SECRET || process.env.FACTORY_STATE_SECRET.length < 32) {
throw new Error('FACTORY_STATE_SECRET must be set (32+ chars) for integrations that sign OAuth state');
}
const config = { ...baseConfig, stateSecret: process.env.FACTORY_STATE_SECRET }; Type guard
function hasStableStateSecret(config) {
return typeof config.stateSecret === 'string' && config.stateSecret.length >= 32;
}
if (!hasStableStateSecret(factoryConfig)) throw new Error('Set a replica-stable stateSecret before boot'); Try / catch
try {
await factory.prepare();
} catch (err) {
if (err.message.includes('replica-stable state secret')) {
throw new Error('Deploy config error: FACTORY_STATE_SECRET missing — OAuth will break across replicas', { cause: err });
}
throw err;
} Prevention
- Set the secret from a shared secret manager, identical on every replica
- Validate required env vars at boot before constructing the factory
- Document stateSecret as required whenever enabling state-signing integrations
- Rotate the secret only via a dual-accept window to avoid invalidating in-flight OAuth flows
When it happens
Trigger: Configuring an integration (e.g. GithubIntegration) whose requiresStableStateSigner is true, while the factory config lacks stateSecret — so stateSigner.stable is false at boot.
Common situations: Deploying behind multiple replicas/instances without a shared secret; local dev working because one process signs and verifies, then breaking in production; forgetting the env var that feeds stateSecret when moving between environments.
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
Related errors
- Redirect URI is required for SSO login
- Google client ID is required. Provide it in the options or s
- Cookie password must be at least 32 characters for SSO. Set
- [MastraAuthGoogle] GOOGLE_COOKIE_PASSWORD is required for Go
- Invalid Google ID token nonce
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/16512087998d6769.
Report an issue: GitHub.