{"record":{"id":"4e29569e865024aa","repo":"n8n-io/n8n","slug":"daemon-spawned-pid-pid-but-did-not-pair-withi","errorCode":null,"errorMessage":"Daemon spawned (pid ${pid}) but did not pair within ${String(PAIRING_TIMEOUT_MS / 1000)}s. Check ${logPath} for errors.","messagePattern":"Daemon spawned \\(pid (.+?)\\) but did not pair within (.+?)s\\. Check (.+?) for errors\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/instance-ai/evaluations/computer-use/daemon.ts","lineNumber":124,"sourceCode":"\t\tusePublished,\n\t\tlogger,\n\t});\n\tlogger.info(`Daemon spawned (pid ${pid}, log: ${logPath})`);\n\tlogger.info('Daemon will keep running after the eval exits — re-runs will reuse it.');\n\n\tconst deadline = Date.now() + PAIRING_TIMEOUT_MS;\n\twhile (Date.now() < deadline) {\n\t\tawait delay(PAIRING_POLL_INTERVAL_MS);\n\t\tstatus = await client.getGatewayStatus();\n\t\tif (status.connected && status.directory) {\n\t\t\tlogger.info(\n\t\t\t\t`Daemon paired in ${String(Math.round((PAIRING_TIMEOUT_MS - (deadline - Date.now())) / 1000))}s`,\n\t\t\t);\n\t\t\treturn toInfo(status);\n\t\t}\n\t}\n\n\tthrow new Error(\n\t\t`Daemon spawned (pid ${pid}) but did not pair within ${String(PAIRING_TIMEOUT_MS / 1000)}s. ` +\n\t\t\t`Check ${logPath} for errors.`,\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction toInfo(status: {\n\tdirectory: string | null;\n\ttoolCategories: Array<{ name: string; enabled: boolean }>;\n}): DaemonInfo {\n\treturn {\n\t\tdirectory: status.directory ?? '',\n\t\tenabledCategories: (status.toolCategories ?? []).filter((c) => c.enabled).map((c) => c.name),\n\t};\n}","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/instance-ai/evaluations/computer-use/daemon.ts#L106-L142","documentation":"Thrown by `ensureDaemon()` after the daemon process was spawned (a pid was returned) but the gateway status poll never reported `connected` within `PAIRING_TIMEOUT_MS` (90s). The loop polls `client.getGatewayStatus()` every 500ms; if no successful pairing happens in 180 polls, the daemon is considered wedged. The message points at the daemon log file (`.eval-output/daemon.log`) which will contain the daemon's stdout/stderr and usually the real cause.","triggerScenarios":"Daemon started but crashed immediately (the spawn-error handler appended `[daemon] spawn failed (...)` to the log but the process lingered long enough to report a pid). Daemon started but the pairing token was rejected by n8n (clock skew, mismatched baseUrl, expired link token). Daemon started but couldn't reach the browser extension / playwright, so it never reached 'connected'. Network/firewall blocking the daemon's loopback webhook back to n8n.","commonSituations":"First run on a machine where the browser extension isn't installed/connected. After changing `--base-url` such that the daemon can reach n8n but the browser side can't reach the daemon. After a Node version change that breaks a native dep in `@n8n/mcp-browser`. On a slow CI box where 90s isn't enough (rare — pairing is usually <10s).","solutions":["Read `.eval-output/daemon.log` (the path in the error) — the daemon's own stderr will state why it didn't pair (port conflict, token error, missing browser, etc.).","If the log shows a spawn failure or crash, rebuild `@n8n/computer-use` and `@n8n/mcp-browser` and try again.","If the log shows the daemon is healthy but pairing never completes, check the browser extension is installed and n8n is reachable from the daemon's host; verify `--base-url` matches what the daemon was spawned with.","If running on a chronically slow machine, you cannot raise the timeout from the CLI (PAIRING_TIMEOUT_MS is a constant); start the daemon manually beforehand so `ensureDaemon` reuses it instead of spawning.","If a previous wedged daemon is still running, kill it (`pkill -f computer-use`) before re-running so the runner doesn't reuse a stuck process."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const info = await ensureDaemon(opts);\n} catch (e) {\n  if (e instanceof Error && /did not pair within/.test(e.message)) {\n    // read the daemon log for the real cause before retrying\n    const log = await readFile(join(opts.evalOutputDir, 'daemon.log'), 'utf-8');\n    throw new Error(`${e.message}\\n--- daemon.log ---\\n${log}`);\n  }\n  throw e;\n}","preventionTips":["When iterating, start the daemon manually once so subsequent runs reuse it instead of re-spawning and re-pairing.","On a slow machine, don't rely on auto-start; pre-pair the daemon.","Always read `.eval-output/daemon.log` after a pairing timeout — the synchronous timeout message hides the asynchronous spawn error written to the log."],"tags":["daemon","computer-use","pairing","timeout","debugging"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}