{"record":{"id":"e555afe2723a5726","repo":"harry0703/MoneyPrinterTurbo","slug":"failed-to-connect-to-sonilo-exc","errorCode":null,"errorMessage":"failed to connect to Sonilo: {exc}","messagePattern":"failed to connect to Sonilo: (.+?)","errorType":"exception","errorClass":"SoniloError","httpStatus":null,"severity":"error","filePath":"app/services/sonilo.py","lineNumber":98,"sourceCode":"\n\ndef test_connection() -> dict[str, Any]:\n    \"\"\"\n    使用不消耗配乐额度的服务列表接口验证 API Key。\n\n    返回原始 JSON 便于 UI 展示可用服务，但日志中绝不记录 Key 或请求头。\n    \"\"\"\n    api_key = get_api_key()\n    if not api_key:\n        raise SoniloError(\"Sonilo API key is required\")\n    try:\n        response = requests.get(\n            f\"{_base_url()}{SERVICES_PATH}\",\n            headers={\"Authorization\": f\"Bearer {api_key}\"},\n            timeout=(15, 30),\n        )\n    except requests.RequestException as exc:\n        raise SoniloError(f\"failed to connect to Sonilo: {exc}\") from exc\n    if not response.ok:\n        raise SoniloError(\n            f\"Sonilo connection failed ({response.status_code}): \"\n            f\"{_safe_response_error(response)}\"\n        )\n    try:\n        payload = response.json()\n    except ValueError as exc:\n        raise SoniloError(\"Sonilo returned an invalid service response\") from exc\n    if not isinstance(payload, dict):\n        raise SoniloError(\"Sonilo returned an unexpected service response\")\n    available_services = payload.get(\"available_services\")\n    if not isinstance(available_services, list) or not all(\n        isinstance(service_id, str) for service_id in available_services\n    ):\n        raise SoniloError(\"Sonilo returned an invalid service list\")\n    normalized_services = {\n        _normalize_service_id(service_id) for service_id in available_services","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/harry0703/MoneyPrinterTurbo/blob/1f9f19c2021a68d04df228f33e9099a0c947f6f8/app/services/sonilo.py#L80-L116","documentation":"Raised by sonilo.test_connection() when the GET to {base_url}/v1/account/services fails with a requests.RequestException — DNS failure, connection refused, TLS error, or timeout (connect 15s / read 30s). The original exception text is embedded and chained with `from exc`, so the network cause is preserved.","triggerScenarios":"requests.get(f'{base_url}/v1/account/services', ...) raising ConnectionError (DNS/host unreachable), ConnectTimeout/ReadTimeout (slow or hanging Sonilo API), or SSLError — e.g. sonilo_base_url misconfigured, api.sonilo.com unreachable from the network, or a proxy interfering.","commonSituations":"sonilo_base_url typo in config.toml (wrong host, trailing path); container/deployment without outbound internet; corporate firewall or self-signed TLS interception blocking api.sonilo.com; Sonilo API outage; read timeout too short on high-latency links (the 30s test read timeout is fixed).","solutions":["Check sonilo_base_url in config.toml — it should be https://api.sonilo.com (or unset to use the default); fix typos","Verify outbound connectivity from the same host/container: curl -v https://api.sonilo.com/v1/account/services","Fix proxy/TLS env (HTTPS_PROXY, REQUESTS_CA_BUNDLE) if behind a corporate proxy; import the proxy CA or set tls_verify appropriately","If Sonilo is down (check status page), retry later — the chained exc text distinguishes DNS vs timeout vs TLS","Ensure container DNS works (docker network, /etc/resolv.conf) when running in k8s/compose"],"exampleFix":"# before (config.toml)\n[app]\nsonilo_base_url = \"https://api.sonilo.com/typo\"  # wrong path -> DNS/404 issues\n\n# after\n[app]\n# omit sonilo_base_url entirely to use the default https://api.sonilo.com","handlingStrategy":"retry","validationCode":"import socket, urllib.parse\nhost = urllib.parse.urlparse(_base_url()).hostname\nsocket.getaddrinfo(host, 443)  # fail fast on DNS/resolution problems","typeGuard":null,"tryCatchPattern":"except SoniloError as e: if 'failed to connect' in str(e): retry with backoff for timeouts/short DNS blips; persistent failure → fix base_url/proxy/DNS; the chained `exc` names the root cause","preventionTips":["Leave sonilo_base_url unset to use the verified default","Ensure containers have outbound DNS and TLS to api.sonilo.com","Configure proxy CA bundles in corporate networks"],"tags":["network","sonilo","dns","timeout","tls"],"backgroundTag":null,"analyzedSha":"1f9f19c2021a68d04df228f33e9099a0c947f6f8","analyzedAt":"2026-08-14T19:41:05.568Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}