{"record":{"id":"d6f3d37e69a26270","repo":"apify/crawlee","slug":"cannot-extend-launchcontext-with-key-key-beca","errorCode":null,"errorMessage":"Cannot extend LaunchContext with key: ${key}, because it's reserved.","messagePattern":"Cannot extend LaunchContext with key: (.+?), because it's reserved\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/browser-pool/src/launch-context.ts","lineNumber":136,"sourceCode":"\n        this.#proxyUrl = proxyUrl;\n\n        // Computed here (not in a field initializer) so that all fields already exist; the accessors live on\n        // the prototype, so they are never own keys and have to be listed explicitly.\n        this.#reservedFieldNames = [...Reflect.ownKeys(this), 'proxyUrl', 'remoteToken', 'extend'];\n    }\n\n    /**\n     * Extend the launch context with any extra fields.\n     * This is useful to keep state information relevant\n     * to the browser being launched. It ensures that\n     * no internal fields are overridden and should be\n     * used instead of property assignment.\n     */\n    extend<T extends Record<PropertyKey, unknown>>(fields: T): void {\n        Object.entries(fields).forEach(([key, value]) => {\n            if (this.#reservedFieldNames.includes(key)) {\n                throw new Error(`Cannot extend LaunchContext with key: ${key}, because it's reserved.`);\n            } else {\n                Reflect.set(this, key, value);\n            }\n        });\n    }\n\n    /**\n     * Sets a proxy URL for the browser.\n     * Use `undefined` to unset existing proxy URL.\n     */\n    set proxyUrl(url: string | undefined) {\n        if (!url) {\n            this.#proxyUrl = undefined;\n            return;\n        }\n\n        const urlInstance = new URL(url);\n        urlInstance.pathname = '/';","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/apify/crawlee/blob/dbe57fb09ca607ad59dcf998f3925ef9ac3bb26c/packages/browser-pool/src/launch-context.ts#L118-L154","documentation":"LaunchContext.extend() lets users attach arbitrary fields to a launch context, but some field names are internally reserved. If you pass a field whose name collides with a reserved internal field (e.g. launchOptions, useIncognitoPages, proxyUrl), extend() refuses to override it so internal behavior cannot be broken. The offending key name is included in the message.","triggerScenarios":"Calling launchContext.extend({ key: value }) where key is one of LaunchContext's #reservedFieldNames (its own internal configuration properties, such as launchOptions, userDataDir, proxyUrl, useIncognitoPages, experimentalContainers).","commonSituations":"Passing a whole options object (e.g. spreading launch options or plugin config) into extend() without filtering out reserved keys; renaming/refactors that introduce a key that collides with an internal field; attempting to override proxy or headless settings via extend instead of the proper constructor/launch options.","solutions":["Remove the reserved key from the object you pass to extend() and set it through the intended API (LaunchContext/PuppeteerPlugin/PlaywrightPlugin constructor options or launchOptions) instead.","Filter the object before extending, e.g. omit keys like launchOptions, userDataDir, proxyUrl, useIncognitoPages.","Check the reserved field list in packages/browser-pool/src/launch-context.ts to know which names are off-limits."],"exampleFix":"// before\nlaunchContext.extend({ proxyUrl: 'http://proxy:8080', myData: 1 });\n// after\nconst { proxyUrl, ...custom } = options; // proxyUrl is reserved\nlaunchContext.extend(custom);\n// configure proxy via the plugin/launch context options instead","handlingStrategy":"validation","validationCode":"const RESERVED = ['launchOptions', 'userDataDir', 'proxyUrl', 'useIncognitoPages', 'experimentalContainers'];\nconst safe = Object.fromEntries(Object.entries(fields).filter(([k]) => !RESERVED.includes(k)));\nlaunchContext.extend(safe);","typeGuard":"function hasReservedKeys(obj: Record<string, unknown>, reserved: string[]): boolean {\n  return Object.keys(obj).some((k) => reserved.includes(k));\n}","tryCatchPattern":"try {\n  launchContext.extend(fields);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Cannot extend LaunchContext')) {\n    console.warn(`Dropping reserved key: ${err.message.match(/key: ([^,]+)/)?.[1]}`);\n  } else throw err;\n}","preventionTips":["Keep custom data under a namespace key (e.g. extend({ custom: {...} })) to avoid collisions.","Never spread raw launch/plugin options objects into extend().","Review the #reservedFieldNames list in launch-context.ts when adding keys."],"tags":["browser-pool","launch-context","reserved-key","api-misuse"],"backgroundTag":"reserved-property-name","analyzedSha":"dbe57fb09ca607ad59dcf998f3925ef9ac3bb26c","analyzedAt":"2026-08-30T22:22:28.328Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}