microsoft/playwright · error · Error

clientCertificates for origin

Error message

clientCertificates for origin "${origin}" mix noCertificate with a real certificate

What it means

SocksClientCertificatesInterceptor builds one TLS secure context per origin. If some clientCertificates entries for an origin have noCertificate=true and others carry real certificate material, the interceptor cannot decide whether to require or bypass client auth for that origin and throws during construction.

Solutions

  1. Ensure all clientCertificates entries for a given origin are consistent: either all noCertificate or all carry real certificates.
  2. Remove the redundant noCertificate entry if the origin should use client certs.
  3. Deduplicate entries per origin after merging configuration from multiple sources.

Example fix

// before
clientCertificates: [
  { origin: 'https://api.example.com', noCertificate: true },
  { origin: 'https://api.example.com', cert: './c.pem', key: './k.pem' }
]
// after
clientCertificates: [
  { origin: 'https://api.example.com', cert: './c.pem', key: './k.pem' }
]
Defensive patterns

Strategy: validation

Validate before calling

const byOrigin = new Map();
for (const c of clientCertificates) {
  const list = byOrigin.get(c.origin) ?? [];
  list.push(!!c.noCertificate);
  byOrigin.set(c.origin, list);
}
for (const [origin, flags] of byOrigin) {
  if (flags.some(Boolean) && flags.some(f => !f))
    throw new Error(`Mixed noCertificate entries for ${origin}`);
}

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Configuring multiple clientCertificates entries for the same origin where at least one has noCertificate: true and at least one has cert/key/pfx — e.g. [{ origin: 'https://api.example.com', noCertificate: true }, { origin: 'https://api.example.com', cert: '...', key: '...' }].

Common situations: Test config merging (e.g. projects with different cert options concatenated) producing mixed entries for a shared origin; environment-specific overrides adding a noCertificate entry alongside the base cert entry.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


AI-assisted analysis of microsoft/playwright@500c9c822c (2026-09-15). Data as JSON: /api/errors/251dc3a4ae5f263e. Report an issue: GitHub.

Appendix: source

Thrown at packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts:337

    if (proxyFromEnv)
      return createProxyAgent({ server: proxyFromEnv });
  }

  _initSecureContexts(clientCertificates: types.BrowserContextOptions['clientCertificates']) {
    // Step 1. Group certificates by origin.
    const origin2certs = new Map<string, NonNullable<types.BrowserContextOptions['clientCertificates']>>();
    for (const cert of clientCertificates || []) {
      const origin = normalizeOrigin(cert.origin);
      const certs = origin2certs.get(origin) || [];
      certs.push(cert);
      origin2certs.set(origin, certs);
    }

    // Step 2. Create secure contexts for each origin.
    for (const [origin, certs] of origin2certs) {
      const noCertificateCount = certs.filter(cert => cert.noCertificate).length;
      if (noCertificateCount > 0 && noCertificateCount < certs.length)
        throw new Error(`clientCertificates for origin "${origin}" mix noCertificate with a real certificate`);
      if (noCertificateCount > 0) {
        this.secureContextMap.set(origin, undefined);
        continue;
      }
      try {
        this.secureContextMap.set(origin, tls.createSecureContext(convertClientCertificatesToTLSOptions(certs)));
      } catch (error) {
        error = rewriteOpenSSLErrorIfNeeded(error);
        throw rewriteErrorMessage(error, `Failed to load client certificate: ${error.message}`);
      }
    }
  }

  public static async create(progress: Progress, contextOptions: Pick<types.BrowserContextOptions, 'clientCertificates' | 'ignoreHTTPSErrors' | 'proxy'>) {
    const proxy = new ClientCertificatesProxy(contextOptions);
    try {
      await progress.race(proxy._socksProxy.listen(0, '127.0.0.1'));
      return proxy;

View on GitHub (pinned to 500c9c822c)