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

  1. Use StagehandPlugin (stagehandPlugin() helper) in the browser pool configuration so _launch registers the instance
  2. Keep the same browser object for the crawl's lifetime; avoid swapping/reconnecting the pool after init
  3. Verify you are using StagehandCrawler with its own browser pool defaults rather than a generic BrowserPool
  4. 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

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


AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30). Data as JSON: /api/errors/6584d7939dae603e. Report an issue: GitHub.