microsoft/playwright · error · Error

Recording is already in progress.

Error message

Recording is already in progress.

What it means

Thrown by startRecording() when a codegen recording session is already active on the tool backend context. The class tracks in-progress recordings via the _recordedActions field; calling startRecording twice without an intervening stopRecording triggers this guard.

Source

Thrown at packages/playwright-core/src/tools/backend/context.ts:243

    this._video = { params, fileName, fileNames: [] };
    const browserContext = await this.ensureBrowserContext();
    for (const page of browserContext.pages())
      await this._startPageVideo(page);
  }

  async stopVideoRecording(): Promise<string[]> {
    if (!this._video)
      return [];
    const video = this._video;
    for (const page of this._rawBrowserContext.pages())
      await page.screencast.stop();
    this._video = undefined;
    return [...video.fileNames];
  }

  async startRecording() {
    if (this._recordedActions)
      throw new Error('Recording is already in progress.');
    const browserContext = await this.ensureBrowserContext() as BrowserContextEx;
    if (typeof browserContext._enableRecorder !== 'function')
      throw new Error('Recording requires a newer version of Playwright, please upgrade.');
    const recordedActions: string[] = [];
    await browserContext._enableRecorder({
      mode: 'recording',
      recorderMode: 'api',
      omitCallTracking: true,
      language: languageGeneratorId(this.codegenLanguage()),
    }, {
      actionAdded: (page, action, code) => {
        recordedActions.push(code);
      },
      actionUpdated: (page, action, code) => {
        if (recordedActions.length)
          recordedActions[recordedActions.length - 1] = code;
        else
          recordedActions.push(code);

View on GitHub (pinned to 312030cdce)

Solutions

  1. Call stopRecording() before starting a new recording session
  2. If a previous session may be dangling, wrap startRecording in logic that stops and discards the old recording first
  3. Avoid retrying startRecording automatically on transient errors, or make retries idempotent by stopping first

Example fix

// before
await context.startRecording();
// ... network retry re-sends:
await context.startRecording(); // throws

// after
try { await context.stopRecording(); } catch {}
await context.startRecording();
Defensive patterns

Strategy: validation

Validate before calling

const inProgress = typeof ctx.stopRecording === 'function' && ctx.isRecording?.(); // if exposed
if (!inProgress) await ctx.startRecording();
// otherwise just always pair: start → actions → stop, and never re-start

Try / catch

try {
  await ctx.startRecording();
} catch (e) {
  if ((e as Error).message.includes('already in progress')) { /* session already active; proceed */ }
  else throw e;
}

Prevention

When it happens

Trigger: Calling startRecording (MCP tool / tool backend API) twice in a row, e.g. a client retry that re-sends the start request after the first succeeded, or two concurrent tool invocations sharing the same browser context.

Common situations: MCP client auto-retries a tool call after a network hiccup; a script loops over pages calling startRecording per page without stopping; stale client state where the user believes recording already stopped but stopRecording was never called.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of microsoft/playwright@312030cdce (2026-08-27). Data as JSON: /api/errors/204e614d2e0a418c. Report an issue: GitHub.