{"record":{"id":"dc1e66ef7a791edd","repo":"jackwener/OpenCLI","slug":"evaluatewithargs-invalid-key-key","errorCode":null,"errorMessage":"evaluateWithArgs: invalid key \"${key}\"","messagePattern":"evaluateWithArgs: invalid key \"(.+?)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/browser/base-page.ts","lineNumber":189,"sourceCode":"  /**\n   * Safely evaluate JS with pre-serialized arguments.\n   * Each key in `args` becomes a `const` declaration with JSON-serialized value,\n   * wrapped in a lexical block to avoid polluting the global execution context.\n   *\n   * Why a block: Chrome's Runtime.evaluate shares a single global context per page.\n   * Top-level `const` declarations persist across calls, so re-declaring the same\n   * variable name (e.g. `markerAttr` in both click resolution and file upload)\n   * throws \"SyntaxError: Identifier has already been declared\". A block keeps\n   * the args scoped without forcing callers to pass expression-only code.\n   *\n   * Usage:\n   *   page.evaluateWithArgs(`(async () => { return sym; })()`, { sym: userInput })\n   */\n  async evaluateWithArgs(js: string, args: Record<string, unknown>): Promise<unknown> {\n    const declarations = Object.entries(args)\n      .map(([key, value]) => {\n        if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)) {\n          throw new Error(`evaluateWithArgs: invalid key \"${key}\"`);\n        }\n        return `const ${key} = ${JSON.stringify(value)};`;\n      })\n      .join('\\n');\n    return this.evaluate(`{\\n${declarations}\\n${js}\\n}`);\n  }\n\n  async fetchJson(url: string, opts: FetchJsonOptions = {}): Promise<unknown> {\n    const request = {\n      url,\n      method: opts.method ?? 'GET',\n      headers: opts.headers ?? {},\n      body: opts.body,\n      hasBody: opts.body !== undefined,\n      timeoutMs: opts.timeoutMs ?? 15_000,\n    };\n\n    const result = await this.evaluateWithArgs(`","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/browser/base-page.ts#L171-L207","documentation":"evaluateWithArgs (src/browser/base-page.ts:189) interpolates each key of the args record as a const declaration (`const ${key} = ${JSON.stringify(value)};`) prepended to the evaluated script. To keep the generated code valid, keys must be valid JavaScript identifiers, enforced by the regex ^[a-zA-Z_$][a-zA-Z0-9_$]*$. Any key that isn't (spaces, dashes, leading digits, empty string) throws immediately.","triggerScenarios":"Passing an args object with keys like {\"data-foo\": 1}, {\"my key\": 2}, {\"1st\": x}, {\"\": v}, or keys containing dots/slashes — typically when keys come from user input, config files, or are derived from element names/labels.","commonSituations":"Mapping HTML attribute names (href, data-id) directly as arg keys; building args from query params or JSON with non-identifier keys; forwarding an arbitrary record without sanitizing key names.","solutions":["Rename keys to valid JS identifiers before calling (camelCase them, or use a fixed safe set of argument names).","Pass the problematic values as a single object under one valid key, e.g. { data: { \"data-foo\": 1 } }, and destructure inside the script.","Sanitize/validate input keys with the same identifier regex at your API boundary.","If the key is dynamic and unavoidable, serialize args into the script via JSON.parse of a string argument instead of identifier declarations."],"exampleFix":"// before\nawait page.evaluateWithArgs(js, { 'data-id': id });\n// after\nawait page.evaluateWithArgs(js, { dataId: id });\n// or\nawait page.evaluateWithArgs(js, { payload: { 'data-id': id } });","handlingStrategy":"validation","validationCode":"const isIdent = (k: string) => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k);\nconst bad = Object.keys(args).filter(k => !isIdent(k));\nif (bad.length) throw new Error(`sanitize arg keys before evaluateWithArgs: ${bad.join(', ')}`);","typeGuard":"function hasIdentifierKeys(args: Record<string, unknown>): args is Record<`string` extends never ? never : string, unknown> & { __valid: true } {\n  return Object.keys(args).every(k => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k));\n}","tryCatchPattern":null,"preventionTips":["Always use fixed, camelCase argument names you control (e.g. { payload, el, text }).","Bundle arbitrary-keyed data under one valid key and destructure inside the script.","Validate user/config-supplied keys with the identifier regex at your API boundary.","Never pass HTML attribute names, query params, or labels directly as arg keys."],"tags":["validation","javascript-identifiers","api-misuse"],"backgroundTag":"invalid-argument-key","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}