microsoft/playwright · error · Error

Invalid glob pattern ${JSON.stringify(glob)}: unmatched '{'

Error message

Invalid glob pattern ${JSON.stringify(glob)}: unmatched '{'

What it means

Thrown by globToRegexPattern after processing all characters when inGroup is still true, i.e. a `{` was opened but never closed with `}`. The incomplete alternation cannot produce a valid regex, so the converter rejects it.

Source

Thrown at packages/isomorphic/urlMatch.ts:90

      case '}':
        if (!inGroup)
          throw new Error(`Invalid glob pattern ${JSON.stringify(glob)}: unmatched '}'`);
        inGroup = false;
        tokens.push(')');
        break;
      case ',':
        if (inGroup) {
          tokens.push('|');
          break;
        }
        tokens.push('\\' + c);
        break;
      default:
        tokens.push(escapedChars.has(c) ? '\\' + c : c);
    }
  }
  if (inGroup)
    throw new Error(`Invalid glob pattern ${JSON.stringify(glob)}: unmatched '{'`);
  tokens.push('$');
  return tokens.join('');
}

function isRegExp(obj: any): obj is RegExp {
  return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]';
}

export type URLMatch = string | RegExp | ((url: URL) => boolean) | URLPattern;
// URLPattern is not in @types/node@18, so we polyfill it ourselves
export type URLPattern = {
  test(input: string | URL): boolean;
  hash: string;
  hostname: string;
  password: string;
  pathname: string;
  port: string;
  protocol: string;

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Add the missing closing `}` to complete the alternation.
  2. Escape the `{` as `\{` if it is meant literally.
  3. Validate brace balance in dynamic glob strings before use.

Example fix

// before
const env = 'staging';
page.route(`**/{${env}`, handler);  // missing }

// after
page.route(`**/{${env}}`, handler);
Defensive patterns

Strategy: validation

Validate before calling

function assertGlobTerminated(glob: string) {
  const opens = (glob.match(/{/g) ?? []).length;
  const closes = (glob.match(/}/g) ?? []).length;
  if (opens !== closes) throw new Error(`Glob braces unbalanced (${opens}/{ vs ${closes}/}): ${glob}`);
}

Type guard

function isTerminatedGlob(glob: string): boolean {
  return (glob.match(/{/g) ?? []).length === (glob.match(/}/g) ?? []).length;
}

Try / catch

try {
  page.route(glob, handler);
} catch (e) {
  if (/unmatched '\{'/.test(e.message)) {
    glob += '}'; // append missing closer when safe to infer
    page.route(glob, handler);
  } else throw e;
}

Prevention

When it happens

Trigger: Passing a glob with an unterminated brace group, e.g. `'{foo,bar'`, `page.route('**/{api', ...)`, or any glob where `{` has no matching `}`. Triggered by the `if (inGroup)` check after the loop.

Common situations: Truncated glob string; template interpolation that omits the closing brace; partial paste of an alternation pattern; off-by-one in dynamic glob construction.

Related errors


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/a2054b9a9fbf5f35. Report an issue: GitHub.