openclaw/openclaw · warning

Playwright connection attempt was superseded.

Error message

Playwright connection attempt was superseded.

What it means

A plain Error thrown when connectEndpoint succeeds but connectionAttempt.cancelled was set to true during the await. The just-opened Browser is retired (tracked for async close) and the connection attempt reports superseded rather than returning a connection the caller no longer wants. This is a cooperative-cancellation mechanism: another caller (closePlaywrightBrowserConnection, a cdpUrl change, or lifecycle teardown) cancelled the in-flight attempt.

Source

Thrown at extensions/browser/src/browser/pw-session-connection.ts:443

          return await withManagedProxyForCdpUrl(connectionUrl, () =>
            withNoProxyForCdpUrl(connectionUrl, () =>
              chromium.connectOverCDP(connectionUrl, { timeout, headers }),
            ),
          );
        };
        let browser: Browser;
        try {
          browser = await connectEndpoint(endpoint);
        } catch (err) {
          if (!isWebSocketUrl(normalized) || endpoint === normalized) {
            throw err;
          }
          browser = await connectEndpoint(normalized);
        }
        if (connectionAttempt.cancelled) {
          connectionAttempt.retired = { browser, cdpUrl: normalized };
          void closeTrackedPlaywrightConnection(connectionAttempt.retired).catch(() => {});
          throw new Error("Playwright connection attempt was superseded.");
        }
        const onDisconnected = () => {
          const current = cachedByCdpUrl.get(normalized);
          if (current?.browser === browser) {
            cachedByCdpUrl.delete(normalized);
          }
        };
        const connected: ConnectedBrowser = { browser, cdpUrl: normalized, onDisconnected };
        cachedByCdpUrl.set(normalized, connected);
        browser.on("disconnected", onDisconnected);
        observeBrowser(browser);
        return connected;
      } catch (err) {
        lastErr = err;
        if (connectionAttempt.cancelled) {
          break;
        }
        // Don't retry rate-limit errors; retrying worsens the 429.

View on GitHub (pinned to 01804a7531)

Solutions

  1. Retry the connect on a superseded error — by the time it propagates, the cancelling caller has usually finished and a fresh attempt will succeed.
  2. Avoid issuing connect and close concurrently on the same cdpUrl; serialize through a single owner.
  3. If this fires during shutdown, treat it as expected and swallow it in the teardown path.
  4. Check for a long-running stale connection holder that keeps cancelling new attempts.

Example fix

// before
await connectBrowser(cdpUrl); // may throw 'superseded' under concurrent close
// after
try { return await connectBrowser(cdpUrl); }
catch (e) { if (isSupersededError(e)) return await connectBrowser(cdpUrl); throw e; }
Defensive patterns

Strategy: retry

Type guard

function isSupersededError(e: unknown): boolean {
  return e instanceof Error && /superseded/.test(e.message);
}

Try / catch

try { return await connectBrowser(cdpUrl); }
catch (e) {
  if (isSupersededError(e)) return await connectBrowser(cdpUrl);
  throw e;
}

Prevention

When it happens

Trigger: Two callers racing on the same cdpUrl: one connects, the other cancels (e.g. profile reconfiguration, gateway shutdown, explicit connection close). The first to land its connectEndpoint wins; the loser sees cancelled and throws. Also fires when the gateway is shutting down mid-connect.

Common situations: Rapid profile/endpoint switching that tears down a previous connection before its connect completes; concurrent open+close on the same cdpUrl; gateway restart during a slow remote connect; test teardown cancelled an in-flight connect.

Related errors


AI-assisted analysis of openclaw/openclaw@01804a7531 (2026-08-12). Data as JSON: /api/errors/eaebc8cce8ec9f08. Report an issue: GitHub.