jackwener/OpenCLI · error · Error

evaluateWithArgs: invalid key "${key}"

Error message

evaluateWithArgs: invalid key "${key}"

What it means

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.

Source

Thrown at src/browser/base-page.ts:189

  /**
   * Safely evaluate JS with pre-serialized arguments.
   * Each key in `args` becomes a `const` declaration with JSON-serialized value,
   * wrapped in a lexical block to avoid polluting the global execution context.
   *
   * Why a block: Chrome's Runtime.evaluate shares a single global context per page.
   * Top-level `const` declarations persist across calls, so re-declaring the same
   * variable name (e.g. `markerAttr` in both click resolution and file upload)
   * throws "SyntaxError: Identifier has already been declared". A block keeps
   * the args scoped without forcing callers to pass expression-only code.
   *
   * Usage:
   *   page.evaluateWithArgs(`(async () => { return sym; })()`, { sym: userInput })
   */
  async evaluateWithArgs(js: string, args: Record<string, unknown>): Promise<unknown> {
    const declarations = Object.entries(args)
      .map(([key, value]) => {
        if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)) {
          throw new Error(`evaluateWithArgs: invalid key "${key}"`);
        }
        return `const ${key} = ${JSON.stringify(value)};`;
      })
      .join('\n');
    return this.evaluate(`{\n${declarations}\n${js}\n}`);
  }

  async fetchJson(url: string, opts: FetchJsonOptions = {}): Promise<unknown> {
    const request = {
      url,
      method: opts.method ?? 'GET',
      headers: opts.headers ?? {},
      body: opts.body,
      hasBody: opts.body !== undefined,
      timeoutMs: opts.timeoutMs ?? 15_000,
    };

    const result = await this.evaluateWithArgs(`

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Rename keys to valid JS identifiers before calling (camelCase them, or use a fixed safe set of argument names).
  2. Pass the problematic values as a single object under one valid key, e.g. { data: { "data-foo": 1 } }, and destructure inside the script.
  3. Sanitize/validate input keys with the same identifier regex at your API boundary.
  4. If the key is dynamic and unavoidable, serialize args into the script via JSON.parse of a string argument instead of identifier declarations.

Example fix

// before
await page.evaluateWithArgs(js, { 'data-id': id });
// after
await page.evaluateWithArgs(js, { dataId: id });
// or
await page.evaluateWithArgs(js, { payload: { 'data-id': id } });
Defensive patterns

Strategy: validation

Validate before calling

const isIdent = (k: string) => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k);
const bad = Object.keys(args).filter(k => !isIdent(k));
if (bad.length) throw new Error(`sanitize arg keys before evaluateWithArgs: ${bad.join(', ')}`);

Type guard

function hasIdentifierKeys(args: Record<string, unknown>): args is Record<`string` extends never ? never : string, unknown> & { __valid: true } {
  return Object.keys(args).every(k => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k));
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/dc1e66ef7a791edd. Report an issue: GitHub.