{"record":{"id":"09e1248b53334cc0","repo":"moeru-ai/airi","slug":"no-candidate-server-channel-url-was-reachable-e","errorCode":null,"errorMessage":"No candidate server channel URL was reachable. ${errors.join('; ')}","messagePattern":"No candidate server channel URL was reachable\\. (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/stage-pocket/src/modules/server-channel-qr-probe.ts","lineNumber":42,"sourceCode":"      connectTimeoutMs: 2_000,\n      name: WebSocketEventSource.StageWeb,\n      token: payload.authToken,\n      url,\n      connector: createTextProtocolConnector(connector),\n    })\n\n    try {\n      await client.connect({ timeout: 2_500 })\n      client.close()\n      return url\n    }\n    catch (error) {\n      client.close()\n      errors.push(`${url}: ${errorMessageFrom(error) ?? 'Unknown websocket probe error'}`)\n    }\n  }\n\n  throw new Error(`No candidate server channel URL was reachable. ${errors.join('; ')}`)\n}\n","sourceCodeStart":24,"sourceCodeEnd":44,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/apps/stage-pocket/src/modules/server-channel-qr-probe.ts#L24-L44","documentation":"Thrown by probeServerChannelQrPayload after every candidate URL in payload.urls failed its websocket handshake/connect probe. Each per-URL failure is captured via errorMessageFrom and aggregated into the message joined by '; '. The host bridge is present (errors [0]/[1] did not fire), but no URL produced a working connection within the 2.5s connect timeout.","triggerScenarios":"The QR payload lists one or more ws/wss URLs and client.connect({ timeout: 2_500 }) rejected for each — causes include the desktop server channel not running, wrong port, firewall blocking the LAN IP, TLS/cert mismatch on wss, expired/invalid authToken, or the host not bound on the advertised interface.","commonSituations":"Desktop AIRI app not running or server-channel service not started; phone on a different subnet than the desktop; port 6121 blocked by firewall; wss URL with a self-signed cert the webview rejects; authToken in the QR no longer valid after a desktop restart; IPv6 link-local address advertised without zone id.","solutions":["Read the aggregated per-URL reasons in the message to identify whether the failure is connection-refused, timeout, TLS, or auth.","Ensure the desktop AIRI app is running and its server-channel service is started, and confirm the advertised host:port matches the phone's reachable network path.","Verify both devices are on the same LAN/subnet and that the server-channel port (default 6121) is not firewalled.","For wss URLs, install/trust the desktop's generated certificate on the mobile device, or disable TLS for local testing.","Re-scan the QR if the authToken may have rotated."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await probeServerChannelQrPayload(payload)\n}\ncatch (error) {\n  const msg = errorMessageFrom(error) ?? ''\n  if (msg.startsWith('No candidate server channel URL was reachable.')) {\n    // parse the per-URL reasons, guide the user (start desktop, check LAN, trust cert)\n  }\n  throw error\n}","preventionTips":["Ensure the desktop AIRI server-channel is started before scanning the QR.","Confirm phone and desktop share a LAN subnet and the server-channel port is open.","Trust the desktop's self-signed cert on the mobile device for wss URLs.","Re-scan the QR if the desktop restarted (authToken may have rotated)."],"tags":["websocket","network","stage-pocket","server-channel","qr-probe","timeout"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}