apify/crawlee · error · Error

Remote browser endpoint resolved to an empty string.

Error message

Remote browser endpoint resolved to an empty string.

What it means

RemoteBrowserPool accepts an endpoint either as a string or a function returning a string/object. When the endpoint is a function, it is awaited and the result validated; if it resolves to an empty string the pool cannot connect, so it throws with this message. It guards against silent misconfiguration producing empty endpoint values.

Solutions

  1. Fix the endpoint function to return a valid ws/http URL; check the source (env var, config, API) for the empty value.
  2. Add a default/fallback or fail fast at startup: throw early if the configured endpoint is empty.
  3. Log the resolved value before returning it from the endpoint function to catch empty resolutions during development.

Example fix

// before
new RemoteBrowserPool({ endpoint: async () => process.env.REMOTE_BROWSER_URL ?? '' });
// after
new RemoteBrowserPool({
  endpoint: async () => {
    const url = process.env.REMOTE_BROWSER_URL;
    if (!url) throw new Error('REMOTE_BROWSER_URL is not set');
    return url;
  },
});
Defensive patterns

Strategy: validation

Validate before calling

const resolved = await endpointFn();
if (typeof resolved === 'string' && !resolved) {
  throw new Error('Remote browser endpoint is empty; check env/config before pool init');
}

Type guard

function isNonEmptyString(v: unknown): v is string {
  return typeof v === 'string' && v.length > 0;
}

Try / catch

try {
  await remotePool.init();
} catch (err) {
  if (err instanceof Error && err.message.includes('resolved to an empty string')) {
    // re-read env/config or retry provisioning, then re-init
  } throw err;
}

Prevention

When it happens

Trigger: Providing a function endpoint to RemoteBrowserPool (or the pool config) that returns '' — e.g. an env var that is unset, an API returning an empty url field, or template interpolation of a missing value.

Common situations: REMOTE_BROWSER_URL environment variable missing/empty in CI or containers; a provisioning endpoint returning an empty body parsed to ''; race where the browser server hasn't published its URL yet.

Related errors


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

Appendix: source

Thrown at packages/browser-pool/src/remote-browser-pool.ts:78

    readonly #onRelease: ((info: { endpoint: string; context?: Record<string, unknown> }) => unknown) | undefined;
    readonly #log: CrawleeLogger;

    constructor(
        endpoint: RemoteBrowserEndpoint,
        onRelease: ((info: { endpoint: string; context?: Record<string, unknown> }) => unknown) | undefined,
        log: CrawleeLogger,
    ) {
        this.#endpoint = endpoint;
        this.#onRelease = onRelease;
        this.#log = log;
    }

    async resolve(options?: { proxyUrl?: string }): Promise<{ url: string; token: number }> {
        const resolved = typeof this.#endpoint === 'function' ? await this.#endpoint(options) : this.#endpoint;

        let result: ResolvedRemoteEndpoint;
        if (typeof resolved === 'string') {
            if (!resolved) throw new Error('Remote browser endpoint resolved to an empty string.');
            result = { url: resolved };
        } else if (!resolved?.url) {
            throw new Error("Remote browser endpoint() must return a URL string or an object with a non-empty 'url'.");
        } else {
            result = resolved;
        }

        const token = this.#nextToken++;
        this.#sessions.set(token, { url: result.url, context: result.context, released: false });
        return { url: result.url, token };
    }

    async release(token: number): Promise<void> {
        const session = this.#sessions.get(token);
        // Release at most once per session — guards a close()/teardown race (the `released` flag is set
        // synchronously before the awaited onRelease, so releaseAll() can't double-fire an in-flight release).
        if (!session || session.released) return;
        session.released = true;

View on GitHub (pinned to dbe57fb09c)