{"record":{"id":"0633d607a7203497","repo":"stablyai/orca","slug":"could-not-attach-debugger-devtools-may-already-be","errorCode":null,"errorMessage":"Could not attach debugger. DevTools may already be open for this tab.","messagePattern":"Could not attach debugger\\. DevTools may already be open for this tab\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/browser/cdp-ws-proxy.ts","lineNumber":220,"sourceCode":"            id: 'orca-proxy-target',\n            webSocketDebuggerUrl: `ws://127.0.0.1:${this.port}`\n          }\n        ])\n      )\n      return\n    }\n    res.writeHead(404)\n    res.end()\n  }\n\n  private async attachDebugger(): Promise<void> {\n    if (this.attached) {\n      return\n    }\n    try {\n      this.debuggerLease = acquireElectronDebugger(this.webContents)\n    } catch {\n      throw new Error('Could not attach debugger. DevTools may already be open for this tab.')\n    }\n    this.attached = true\n\n    // Why: attaching the CDP debugger sets navigator.webdriver = true and\n    // exposes other automation signals that Cloudflare Turnstile checks.\n    // Inject before any page loads so challenges succeed.\n    try {\n      await this.webContents.debugger.sendCommand('Page.enable', {})\n      await this.webContents.debugger.sendCommand('Page.addScriptToEvaluateOnNewDocument', {\n        source: ANTI_DETECTION_SCRIPT\n      })\n    } catch {\n      /* best-effort — page domain may not be ready yet */\n    }\n\n    this.debuggerMessageHandler = (_event: unknown, ...rest: unknown[]) => {\n      const [method, params, sessionId] = rest as [\n        string,","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/browser/cdp-ws-proxy.ts#L202-L238","documentation":"Thrown when CdpWsProxy.attachDebugger() cannot acquire the Electron CDP debugger on a WebContents. The underlying acquireElectronDebugger() either fails to call webContents.debugger.attach('1.3') (the tab is destroyed, DevTools already holds the session, or a prior attach errored), so the catch wraps any failure into this user-facing message. A WebContents only permits one debug protocol attachment at a time; a second consumer is rejected by Electron.","triggerScenarios":"Calling CdpWsProxy.start() on a tab where DevTools is already open (F12), calling start() twice without stop(), attaching after the WebContents was destroyed, or another internal consumer (e.g. a screenshot/PDF helper) already holding the debugger lease and crashing without releasing it.","commonSituations":"User opens DevTools manually on the agent browser tab and then triggers a CDP-driven action (screenshot, print-to-pdf, automation). A previous CdpWsProxy crashed mid-session leaving the debugger attached. CI runs that launch headless automation while a DevTools window is docked.","solutions":["Close any open DevTools window on the target tab and retry the operation.","Ensure CdpWsProxy.stop() / detachDebugger() ran from the previous session before starting a new one.","Check webContents.isDestroyed() before attaching and skip/teardown if true.","Coordinate all CDP consumers through the ElectronDebuggerLease ref-count so only one attach path exists."],"exampleFix":"// before\ntry {\n  this.debuggerLease = acquireElectronDebugger(this.webContents)\n} catch {\n  throw new Error('Could not attach debugger. DevTools may already be open for this tab.')\n}\n\n// after\nif (this.webContents.isDestroyed()) {\n  throw new Error('Browser tab is no longer available')\n}\nif (this.webContents.debugger.isAttached()) {\n  throw new Error('Could not attach debugger. DevTools may already be open for this tab.')\n}\nthis.debuggerLease = acquireElectronDebugger(this.webContents)","handlingStrategy":"validation","validationCode":"// Run before CdpWsProxy.start()\nfunction canAttachDebugger(webContents: WebContents): boolean {\n  return !webContents.isDestroyed() && !webContents.debugger.isAttached()\n}\nif (!canAttachDebugger(this.webContents)) {\n  throw new Error('DevTools or another debugger is already attached.')\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always call CdpWsProxy.stop() (which detaches) before discarding a proxy instance.","Use the ElectronDebuggerLease ref-count so multiple CDP consumers share one attach.","Close DevTools on the target tab before starting CDP automation.","Guard isDestroyed() before attach to avoid attaching a dead WebContents."],"tags":["electron","cdp","debugger","devtools"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}