{"record":{"id":"7cd7c77fd65fb668","repo":"Panniantong/Agent-Reach","slug":"curl-is-unavailable-for-the-v2ex-tls-fallback","errorCode":null,"errorMessage":"curl is unavailable for the V2EX TLS fallback","messagePattern":"curl is unavailable for the V2EX TLS fallback","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"agent_reach/channels/v2ex.py","lineNumber":90,"sourceCode":"                or \"eof occurred in violation of protocol\" in text\n            ):\n                return True\n        for nested in (\n            getattr(current, \"reason\", None),\n            current.__cause__,\n            current.__context__,\n        ):\n            if isinstance(nested, BaseException):\n                pending.append(nested)\n    return False\n\n\ndef _get_json_with_curl(url: str) -> Any:\n    \"\"\"Fetch bounded JSON with the OS curl TLS stack.\"\"\"\n    _validate_api_url(url)\n    curl = shutil.which(\"curl\")\n    if not curl:\n        raise RuntimeError(\"curl is unavailable for the V2EX TLS fallback\")\n\n    command = [\n        curl,\n        \"--fail\",\n        \"--silent\",\n        \"--show-error\",\n        \"--proto\",\n        \"=https\",\n        \"--connect-timeout\",\n        \"5\",\n        \"--max-time\",\n        str(_TIMEOUT),\n        \"--max-filesize\",\n        str(_MAX_RESPONSE_BYTES),\n        \"--header\",\n        f\"User-Agent: {_UA}\",\n        \"--url\",\n        url,","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/Panniantong/Agent-Reach/blob/93ae1d18c37b707dec053c7c4f9d91cd8ef8943d/agent_reach/channels/v2ex.py#L72-L108","documentation":"V2EX channel fetches JSON with urllib first; when Python's TLS stack fails with a known TLS-EOF error, it retries with the OS curl binary (see _get_json_with_curl in agent_reach/channels/v2ex.py:80). This RuntimeError is raised when shutil.which('curl') finds no curl executable, so the TLS fallback path cannot run. It means the environment lacks curl or curl is not on PATH.","triggerScenarios":"Any V2EXChannel.read()/search() call where urllib first raises an unexpected-TLS-EOF error and _get_json escalates to _get_json_with_curl, on a host where `curl` is not installed or PATH is stripped (minimal containers, alpine without curl, systemd services with limited PATH).","commonSituations":"Running agent-reach inside slim Docker images (python:3.x-slim has no curl), restricted CI runners, NixOS/Homebrew environments where curl is not linked into the shell PATH, or subprocess environments where PATH is sanitized.","solutions":["Install curl on the host (apt-get install curl / apk add curl / brew install curl)","Verify `which curl` resolves in the same environment agent-reach runs in (print os.environ['PATH'])","If curl exists but is not found, add its directory to PATH or invoke agent-reach from a shell where PATH includes /usr/bin","As a library workaround, call V2EX URLs directly with your own HTTP client when this error surfaces"],"exampleFix":"# before: slim Dockerfile with no curl\nFROM python:3.12-slim\nRUN pip install agent-reach\n\n# after: install curl so the V2EX TLS fallback works\nFROM python:3.12-slim\nRUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*\nRUN pip install agent-reach","handlingStrategy":"validation","validationCode":"import shutil\n\ndef v2ex_fallback_available() -> bool:\n    \"\"\"True when the curl TLS fallback can run on this host.\"\"\"\n    return shutil.which(\"curl\") is not None\n\n# before calling V2EXChannel\nif not v2ex_fallback_available():\n    raise SystemExit(\"install curl, or route V2EX reads through your own HTTP client\")","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Base your agent-reach container image on one that ships curl, or add an explicit curl install step","Smoke-test deployments with `shutil.which('curl')` before enabling V2EX-dependent features","Treat this RuntimeError as an environment defect: fix the host rather than catching and ignoring"],"tags":["v2ex","network","curl","environment","tls"],"backgroundTag":null,"analyzedSha":"93ae1d18c37b707dec053c7c4f9d91cd8ef8943d","analyzedAt":"2026-08-14T22:54:06.735Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}