{"record":{"id":"c255c1bc4c9fb386","repo":"microsoft/playwright","slug":"tracing-is-already-stopping","errorCode":null,"errorMessage":"Tracing is already stopping","messagePattern":"Tracing is already stopping","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/server/trace/recorder/tracing.ts","lineNumber":334,"sourceCode":"    state.traceName = name;\n    state.chunkOrdinal = 0;  // Reset ordinal for the new name.\n    this._allocateNewTraceFile(state);\n\n    const newNetworkFile = path.join(state.tracesDir, name + '.network');\n    if (preserveNetworkResources)\n      this._fs.copyFile(state.networkFile, newNetworkFile);\n    state.networkFile = newNetworkFile;\n  }\n\n  async stop(progress: Progress) {\n    await progress.race(this._stop());\n  }\n\n  private async _stop() {\n    if (!this._state)\n      return;\n    if (this._isStopping)\n      throw new Error(`Tracing is already stopping`);\n    if (this._state.recording)\n      throw new Error(`Must stop trace file before stopping tracing`);\n    this._closeAllGroups();\n    this._harTracer.stop();\n    this.flushHarEntries();\n    await this._fs.syncAndGetError().finally(() => {\n      this._state = undefined;\n    });\n  }\n\n  async deleteTmpTracesDir() {\n    if (this._tracesTmpDir)\n      await removeFolders([this._tracesTmpDir]);\n  }\n\n  private _createTracesDirIfNeeded() {\n    if (this._precreatedTracesDir)\n      return this._precreatedTracesDir;","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/server/trace/recorder/tracing.ts#L316-L352","documentation":"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.","triggerScenarios":"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.","commonSituations":"An afterEach hook calls tracing.stop() while the runner's own teardown is also stopping; promise chains that fire stop twice on error paths.","solutions":["Call tracing.stop() exactly once and await it.","Ensure stopChunk has fully resolved before calling stop().","De-duplicate stop calls in your teardown (a single finally block owns the stop)."],"exampleFix":"// before\nawait context.tracing.stopChunk({ path: 't.zip' }); // sets _isStopping briefly\nawait context.tracing.stop(); // may race\n\n// after\nawait context.tracing.stopChunk({ path: 't.zip' });\n// _isStopping clears after the await; safe to stop\nawait context.tracing.stop();","handlingStrategy":"validation","validationCode":"// Call stop exactly once after stopChunk resolves.\nlet stopped = false;\nasync function stopOnce(ctx) {\n  if (stopped) return;\n  stopped = true;\n  await ctx.tracing.stop();\n}","typeGuard":null,"tryCatchPattern":"try { await context.tracing.stop(); }\ncatch (e) {\n  if (/already stopping/i.test(String(e.message))) return; // idempotent\n  throw e;\n}","preventionTips":["Own the stop in a single finally block; do not call it from two teardown paths.","Ensure stopChunk fully resolved before stop().","Treat stop as idempotent in your orchestration layer."],"tags":["tracing","state-machine","concurrency"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}