{"record":{"id":"166a74da05b5e1f4","repo":"apify/crawlee","slug":"page-with-id-id-already-exists","errorCode":null,"errorMessage":"Page with ID: ${id} already exists.","messagePattern":"Page with ID: (.+?) already exists\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/browser-pool/src/browser-pool.ts","lineNumber":469,"sourceCode":"     * for `proxyUrl` and `ignoreTlsErrors` respectively. Explicit `proxyUrl` /\n     * `ignoreTlsErrors` values in the options take precedence.\n     *\n     * Beyond fingerprint caching and proxy configuration, no other session\n     * properties are consumed — cookie and header injection remain the\n     * crawler's responsibility.\n     */\n    async newPage(options: BrowserPoolNewPageOptions<PageOptions, BrowserPlugins[number]> = {}): Promise<PageReturn> {\n        const {\n            id = nanoid(),\n            pageOptions,\n            browserPlugin = this.pickBrowserPlugin(),\n            session,\n            proxyUrl = session?.proxyInfo?.url,\n            ignoreTlsErrors = session?.proxyInfo?.ignoreTlsErrors,\n        } = options;\n\n        if (this.pages.has(id)) {\n            throw new Error(`Page with ID: ${id} already exists.`);\n        }\n\n        if (browserPlugin && !this.browserPlugins.includes(browserPlugin)) {\n            throw new Error('Provided browserPlugin is not one of the plugins used by BrowserPool.');\n        }\n\n        // Bind the limiter callback to the current async-hooks context. p-limit\n        // otherwise resumes queued callbacks in the previous task's\n        // AsyncLocalStorage context, leaking aborted cancelTasks across unrelated\n        // requests (https://github.com/apify/crawlee/issues/3670). Mirrors the\n        // fix p-limit landed upstream in v5 (sindresorhus/p-limit#71); v5 is an\n        // ESM-only rewrite, so we can't bump it in Crawlee v3.\n        // Besides the cancelTask leak, the wrapper also keeps the per-request *storage transaction*\n        // ALS-scoped: without it, a queued callback would resume in the previous request's async\n        // context and run request B's storage writes inside request A's transaction.\n        // TODO(crawlee@v4): bump p-limit to v5 and drop this AsyncResource.bind wrapper.\n        // Limiter is necessary - https://github.com/apify/crawlee/issues/1126\n        return this.#limiter(","sourceCodeStart":451,"sourceCodeEnd":487,"githubUrl":"https://github.com/apify/crawlee/blob/dbe57fb09ca607ad59dcf998f3925ef9ac3bb26c/packages/browser-pool/src/browser-pool.ts#L451-L487","documentation":"newPage() generates or accepts a page id and tracks in-flight page creation promises in this.pages to prevent duplicate concurrent creation under the same id. This plain Error is thrown when the given (or default nanoid) id already has an entry, because two pages with the same id would corrupt the pool's page bookkeeping.","triggerScenarios":"Calling pool.newPage({ id: 'same-id' }) (or page()/pagePromises() which delegate to newPage) twice with the same id while the first page is still being created or tracked; reusing an id after an earlier newPage that hasn't completed/cleaned up.","commonSituations":"Retry logic that reuses a request/page id without waiting for the first attempt; request-key collisions when mapping crawler requests to page ids; creating pages in a loop with a constant id.","solutions":["Use unique ids per newPage call (omit `id` to let nanoid generate one).","Await the previous page promise before creating another page with the same id.","Catch the error and generate a fresh id, or check `pool.pages.has(id)` before calling."],"exampleFix":"// before\nawait pool.newPage({ id: requestId });\nawait pool.newPage({ id: requestId }); // throws\n// after\nconst id = pool.pages.has(requestId) ? `${requestId}-${nanoid()}` : requestId;\nawait pool.newPage({ id });","handlingStrategy":"validation","validationCode":"if (pool.pages.has(id)) id = `${id}-${nanoid()}`;\nawait pool.newPage({ id });","typeGuard":null,"tryCatchPattern":"try {\n    await pool.newPage({ id });\n} catch (err) {\n    if (err.message.startsWith('Page with ID:')) {\n        await pool.newPage({ id: `${id}-${nanoid()}` });\n    } else throw err;\n}","preventionTips":["Omit `id` to auto-generate a unique one","Never reuse ids without awaiting the previous page promise","Map request keys to unique page ids via nanoid suffix"],"tags":["duplicate-id","api-misuse","browser-pool"],"backgroundTag":"duplicate-resource-id","analyzedSha":"dbe57fb09ca607ad59dcf998f3925ef9ac3bb26c","analyzedAt":"2026-08-30T22:22:28.328Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}