microsoft/playwright · error · Error

Tracing is already stopping

Error message

Tracing is already stopping

What it means

Tracing.stop() (via _stop()) throws if a stop is already in flight (the _isStopping flag is set from an ongoing stopChunk). Stopping involves flushing the SerializedFS and clearing _state; a second concurrent stop would double-flush and reset state prematurely.

Source

Thrown at packages/playwright-core/src/server/trace/recorder/tracing.ts:334

    state.traceName = name;
    state.chunkOrdinal = 0;  // Reset ordinal for the new name.
    this._allocateNewTraceFile(state);

    const newNetworkFile = path.join(state.tracesDir, name + '.network');
    if (preserveNetworkResources)
      this._fs.copyFile(state.networkFile, newNetworkFile);
    state.networkFile = newNetworkFile;
  }

  async stop(progress: Progress) {
    await progress.race(this._stop());
  }

  private async _stop() {
    if (!this._state)
      return;
    if (this._isStopping)
      throw new Error(`Tracing is already stopping`);
    if (this._state.recording)
      throw new Error(`Must stop trace file before stopping tracing`);
    this._closeAllGroups();
    this._harTracer.stop();
    this.flushHarEntries();
    await this._fs.syncAndGetError().finally(() => {
      this._state = undefined;
    });
  }

  async deleteTmpTracesDir() {
    if (this._tracesTmpDir)
      await removeFolders([this._tracesTmpDir]);
  }

  private _createTracesDirIfNeeded() {
    if (this._precreatedTracesDir)
      return this._precreatedTracesDir;

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Call tracing.stop() exactly once and await it.
  2. Ensure stopChunk has fully resolved before calling stop().
  3. De-duplicate stop calls in your teardown (a single finally block owns the stop).

Example fix

// before
await context.tracing.stopChunk({ path: 't.zip' }); // sets _isStopping briefly
await context.tracing.stop(); // may race

// after
await context.tracing.stopChunk({ path: 't.zip' });
// _isStopping clears after the await; safe to stop
await context.tracing.stop();
Defensive patterns

Strategy: validation

Validate before calling

// Call stop exactly once after stopChunk resolves.
let stopped = false;
async function stopOnce(ctx) {
  if (stopped) return;
  stopped = true;
  await ctx.tracing.stop();
}

Try / catch

try { await context.tracing.stop(); }
catch (e) {
  if (/already stopping/i.test(String(e.message))) return; // idempotent
  throw e;
}

Prevention

When it happens

Trigger: Calling context.tracing.stop() while context.tracing.stopChunk() is mid-flight; calling stop() twice in parallel; the runner and user code both calling stop on the same context.

Common situations: An afterEach hook calls tracing.stop() while the runner's own teardown is also stopping; promise chains that fire stop twice on error paths.

Related errors


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