mastra-ai/mastra · error
createStagehand factory not set - required for thread scope
Error message
createStagehand factory not set - required for thread scope
What it means
In 'thread' scope, the StagehandThreadManager creates a dedicated Stagehand instance per thread by invoking an injected createStagehand factory. If that factory was never provided at construction, the manager cannot fulfill thread-scoped session creation and throws. This is a configuration/dependency-injection error, not a runtime browser problem.
Source
Thrown at browser/stagehand/src/thread-manager.ts:88
}
/**
* Create a new session for a thread.
*/
protected override async createSession(threadId: string): Promise<StagehandThreadSession> {
// Check for saved browser state before creating new session (for browser restore)
const savedState = this.getSavedBrowserState(threadId);
const session: StagehandThreadSession = {
threadId,
createdAt: Date.now(),
browserState: savedState,
};
if (this.scope === 'thread') {
// Full thread scope - create a new Stagehand instance
if (!this.createStagehand) {
throw new Error('createStagehand factory not set - required for thread scope');
}
this.logger?.debug?.(`Creating dedicated Stagehand instance for thread ${threadId}`);
const stagehand = await this.createStagehand();
session.stagehand = stagehand;
this.threadManagers.set(threadId, stagehand);
// Restore browser state if available (before notifying parent to avoid screencast race)
if (savedState && savedState.tabs.length > 0) {
this.logger?.debug?.(`Restoring browser state for thread ${threadId}: ${savedState.tabs.length} tabs`);
await this.restoreBrowserState(stagehand, savedState);
}
// Notify parent browser so it can set up close listeners
// This is done after restoration so the screencast starts on the correct active page
this.onBrowserCreated?.(stagehand, threadId);
}
// For 'shared' scope, no session setup needed - all threads share the instanceView on GitHub (pinned to 75dd419e61)
Solutions
- Pass a createStagehand factory when constructing StagehandBrowser / the thread manager
- Ensure your constructor config actually enables thread scope intentionally and supplies the factory
- Update wiring code after library upgrades that made the factory required for thread scope
- Fall back to shared scope if per-thread instances are not needed
Example fix
// before
const browser = new StagehandBrowser({ scope: 'thread' });
// after
const browser = new StagehandBrowser({
scope: 'thread',
threadManagerOptions: {
createStagehand: async () => new Stagehand({ env: 'LOCAL', ... }),
},
}); Defensive patterns
Strategy: validation
Validate before calling
if (!threadManager.createStagehand) {
throw new Error('thread scope requires a createStagehand factory at construction');
} Type guard
function supportsThreadScope(m: { scope: string; createStagehand?: unknown }):
m is { scope: 'thread'; createStagehand: () => Promise<Stagehand> } {
return m.scope === 'thread' && typeof m.createStagehand === 'function';
} Try / catch
try {
await threadManager.createSession(threadId);
} catch (e) {
if (e instanceof Error && e.message.includes('createStagehand factory not set')) {
throw new Error('Misconfiguration: wire createStagehand when scope=thread');
}
throw e;
} Prevention
- Always supply createStagehand when constructing a thread-scoped manager
- Type the options so scope='thread' requires the factory (discriminated union)
- Add a construction-time assertion if scope is dynamic
- Review wiring after upgrades that change thread-scope requirements
When it happens
Trigger: Calling createSession with scope='thread' on a StagehandBrowser whose thread manager was constructed without a createStagehand callback.
Common situations: Constructing the browser/thread manager manually without wiring the factory; copying setup code that assumed shared scope but switching to thread scope; library version upgrades requiring the factory for thread scope; DI container not providing the factory.
Related errors
- Okta domain is required. Provide it in the options or set OK
- Okta API token is required for RBAC. Provide it in the optio
- Supabase URL and anon key are required, please provide them
- WorkOS API key and client ID are required. Provide them in t
- Cookie password must be at least 32 characters. Set WORKOS_C
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/efdb9b75a3710ff9.
Report an issue: GitHub.