{"record":{"id":"16721452ac098d87","repo":"microsoft/playwright","slug":"browser-context-was-closed-before-the-daemon-could","errorCode":null,"errorMessage":"Browser context was closed before the daemon could start","messagePattern":"Browser context was closed before the daemon could start","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/playwright-core/src/tools/cli-daemon/daemon.ts","lineNumber":77,"sourceCode":"  }\n): Promise<string> {\n  const sessionConfig = createSessionConfig(clientInfo, sessionName, browserInfo, options);\n  const { socketPath } = sessionConfig;\n\n  // Clean up existing socket file on Unix\n  if (process.platform !== 'win32' && await socketExists(socketPath)) {\n    try {\n      await fs.promises.unlink(socketPath);\n    } catch (error) {\n      throw error;\n    }\n  }\n\n  const backend = new BrowserBackend(contextConfig, browserContext, browserTools);\n  await backend.initialize(mcpClientInfo);\n\n  if (browserContext.isClosed())\n    throw new Error('Browser context was closed before the daemon could start');\n\n  const server = net.createServer(socket => {\n    const connection = new SocketConnection(socket);\n    const abortController = new AbortController();\n    connection.onclose = () => abortController.abort();\n    connection.onmessage = async message => {\n      const { id, method, params } = message;\n      try {\n        if (method === 'stop') {\n          await deleteSessionFile(clientInfo, sessionConfig);\n          const sendAck = async () => connection.send({ id, result: 'ok' }).catch(() => {});\n          if (options?.exitOnClose)\n            gracefullyProcessExitDoNotHang(0, () => sendAck());\n          else\n            await sendAck();\n        } else if (method === 'run') {\n          const { toolName, toolParams } = parseCliCommand(params.args);\n          toolParams._meta = { cwd: params.cwd, raw: params.raw || params.json, json: !!params.json };","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/tools/cli-daemon/daemon.ts#L59-L95","documentation":"Thrown by `startCliDaemonServer` after `BrowserBackend.initialize()` completes, if the supplied `BrowserContext` is already closed. The daemon cannot serve tools against a dead context, so it aborts during startup. This is a race-condition guard, not a normal flow.","triggerScenarios":"The browser/context is closed (manually, by a SIGINT handler, by the owning process exiting, or by a parallel `stop` call) in the window between `startCliDaemonServer` being called and the `isClosed()` check at daemon.ts:77. Also reachable if `createBrowserWithInfo` returns a context that is closed before the daemon server is stood up.","commonSituations":"Caller tears down the context immediately after requesting daemon start; a shared/attached browser's owner process exits mid-init; tests that start a daemon on a context fixture that is already disposed; an `exitOnClose` daemon whose context is closed by a prior `stop` arriving during boot.","solutions":["Ensure the `BrowserContext` passed to `startCliDaemonServer` stays open until the server is listening (await the returned socket path before disposing).","If the context is shared/attached, confirm the owning process is still alive and not itself shutting down.","Move any `context.close()` / `browser.close()` calls to run only after the daemon has fully started (or stop the daemon first)."],"exampleFix":"// before\nconst ctx = await browser.newContext();\nstartCliDaemonServer('s', ctx, ...); // fire-and-forget\nawait ctx.close();\n// after\nconst ctx = await browser.newContext();\nawait startCliDaemonServer('s', ctx, ...); // await startup\n// ... use daemon ...\nawait ctx.close();","handlingStrategy":"try-catch","validationCode":"// Before starting the daemon, ensure the context is alive.\nfunction isContextUsable(ctx: any): boolean {\n  return !!ctx && typeof ctx.isClosed === 'function' && !ctx.isClosed();\n}","typeGuard":null,"tryCatchPattern":"try {\n  await startCliDaemonServer(session, ctx, info, cfg, client, mcp, opts);\n} catch (e) {\n  if (/Browser context was closed before the daemon could start/.test((e as Error).message)) {\n    // context died during init — restart browser/context then retry, or surface to caller\n  }\n  throw e;\n}","preventionTips":["Await startCliDaemonServer before allowing any code path to close the context.","Keep the context's owning process alive for the daemon's lifetime.","Register the context close listener only after the daemon socket path is returned."],"tags":["daemon","lifecycle","race-condition","browser-context"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}