apify/crawlee · error · Error

Cannot activate BrowserController without an assigned browse

Error message

Cannot activate BrowserController without an assigned browser.

What it means

BrowserController.activate() is the entry point that marks a controller as active after a browser has been attached. The library throws this error when activate() is called while the internal `this.browser` field is still unset, because there is nothing to activate. It is a lifecycle-order guard: the controller must first have a browser assigned (via assignBrowser) before activation makes sense.

Source

Thrown at packages/browser-pool/src/abstract-classes/browser-controller.ts:180

    #hasBrowserPromise = new Promise<void>((resolve) => {
        this.#commitBrowser = resolve;
    });

    constructor(browserPlugin: BrowserPlugin<Library, LibraryOptions, LaunchResult, NewPageOptions, NewPageResult>) {
        super();
        this.log = serviceLocator.getLogger().child({ prefix: 'BrowserPool' });
        this.browserPlugin = browserPlugin;
    }

    /**
     * Activates the BrowserController. If you try to open new pages before
     * activation, the pages will get queued and will only be opened after
     * activate is called.
     * @ignore
     */
    activate(): void {
        if (!this.browser) {
            throw new Error('Cannot activate BrowserController without an assigned browser.');
        }
        this.#activate();
        this.isActive = true;
    }

    /**
     * @ignore
     */
    assignBrowser(
        browser: LaunchResult,
        launchContext: LaunchContext<Library, LibraryOptions, LaunchResult, NewPageOptions, NewPageResult>,
    ): void {
        if (this.browser) {
            throw new Error('BrowserController already has a browser instance assigned.');
        }
        this.browser = browser;
        this.launchContext = launchContext;
        this.#commitBrowser();

View on GitHub (pinned to dbe57fb09c)

Solutions

  1. Do not call activate() yourself — use BrowserPool.newPage() or newPageInNewBrowser() which handle assignment and activation in the correct order.
  2. If you must manage the controller manually, call controller.assignBrowser(launchResult, launchContext) before activate().
  3. Check that your custom plugin's _launch implementation actually returns/commits a browser so assignBrowser is reached.

Example fix

// before
const controller = new PlaywrightBrowserController(options);
controller.activate(); // throws
// after
const controller = new PlaywrightBrowserController(options);
controller.assignBrowser(await playwright.launch(), launchContext);
controller.activate();
Defensive patterns

Strategy: validation

Validate before calling

function canActivate(controller) { return controller.browser != null && !controller.isActive; }
if (!canActivate(controller)) throw new Error('assign a browser before activate()');

Type guard

function hasBrowser(c) { return c.browser !== undefined && c.browser !== null; }

Prevention

When it happens

Trigger: Calling browserController.activate() directly (it is public despite the @ignore tag) on a freshly constructed controller, or on a controller that was never assigned a LaunchResult — e.g. instantiating a controller subclass manually instead of going through BrowserPool.newPage()/newPageInNewBrowser().

Common situations: Developers subclassing BrowserController and managing the lifecycle by hand; calling internal-ish methods out of order; plugins with custom _launch logic that never commit a browser before activation; version changes where activation used to implicitly launch.

Related errors


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