apify/crawlee · error
Stagehand instance not found for browser
Error message
Stagehand instance not found for browser
What it means
StagehandController.getStagehand() lazily resolves the Stagehand instance for this controller's browser from a shared #stagehandInstances WeakMap and caches it. The error means the map has no entry for this controller's browser — i.e. the Stagehand instance was never registered for that browser object.
Source
Thrown at packages/stagehand-crawler/src/internals/stagehand-controller.ts:39
* @ignore
*/
export class StagehandController extends BrowserController<BrowserType, LaunchOptions, PlaywrightBrowser> {
#stagehand: Stagehand | null = null;
readonly #stagehandInstances: WeakMap<PlaywrightBrowser, Stagehand>;
constructor(browserPlugin: StagehandPlugin, stagehandInstances: WeakMap<PlaywrightBrowser, Stagehand>) {
super(browserPlugin);
this.#stagehandInstances = stagehandInstances;
}
/**
* Gets the Stagehand instance associated with this controller's browser.
*/
getStagehand(): Stagehand {
if (!this.#stagehand) {
this.#stagehand = this.#stagehandInstances.get(this.browser)!;
if (!this.#stagehand) {
throw new Error('Stagehand instance not found for browser');
}
}
return this.#stagehand;
}
/**
* Creates a new page using the browser's default context.
* We use Playwright's browser API directly since we connected via CDP.
*/
protected override async _newPage(_contextOptions?: unknown): Promise<Page> {
try {
// Get the default context from the Playwright browser (connected via CDP)
const contexts = this.browser.contexts();
if (contexts.length === 0) {
throw new Error('No browser context available');
}
const context = contexts[0];View on GitHub (pinned to dbe57fb09c)
Solutions
- Use StagehandPlugin (stagehandPlugin() helper) in the browser pool configuration so _launch registers the instance
- Keep the same browser object for the crawl's lifetime; avoid swapping/reconnecting the pool after init
- Verify you are using StagehandCrawler with its own browser pool defaults rather than a generic BrowserPool
- Add the instance manually only as a last resort: controller/'plugin' #stagehandInstances.set(browser, stagehand)
Example fix
// before
const crawler = new StagehandCrawler({
browserPoolOptions: { useFingerprints: false }, // plugin missing
});
// after
import { StagehandPlugin } from '@crawlee/stagehand-crawler';
const crawler = new StagehandCrawler({
browserPoolOptions: {
browserPlugins: [new StagehandPlugin()],
},
}); Defensive patterns
Strategy: validation
Validate before calling
const stagehandPlugin = new StagehandPlugin();
if (!browserPoolOptions.browserPlugins?.some((p) => p instanceof StagehandPlugin)) {
throw new Error('StagehandCrawler requires StagehandPlugin in browserPoolOptions.browserPlugins');
} Type guard
function hasStagehand(controller) {
return typeof controller?.getStagehand === 'function' && controller.getStagehand() != null;
} Try / catch
try {
const stagehand = controller.getStagehand();
} catch (err) {
if (err.message.includes('Stagehand instance not found')) {
throw new Error('Browser pool is not using StagehandPlugin; reconfigure browserPlugins');
}
throw err;
} Prevention
- Always configure the pool with StagehandPlugin when using StagehandCrawler
- Do not replace or reconnect browser instances after plugin launch
- Keep one crawler/pool instance per Stagehand setup
- Log the pool's plugin list at startup to catch misconfiguration early
When it happens
Trigger: Calling controller.getStagehand() (or stagehand-crawler's setUpStagehand → getStagehand) when the browser was NOT launched through StagehandPlugin._launch, the browser instance was recreated after the map entry was set (WeakMap keyed by old browser object), or a user-supplied browser pool/generator bypasses the plugin's launch path.
Common situations: Mixing a plain Playwright browser pool with StagehandCrawler, reconfiguring the pool mid-run so a new browser replaces the original, or constructing a StagehandController manually without running the plugin.
Related errors
- Could not resolve StagehandController for page — is the brow
- ${crawlerName}: ${names.map((name) => `\`${name}\``).join(',
- Provided browserPlugin is not one of the plugins used by Bro
- Remote browser endpoint resolved to an empty string.
- Remote browser endpoint() must return a URL string or an obj
AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30).
Data as JSON: /api/errors/6584d7939dae603e.
Report an issue: GitHub.