Panniantong/Agent-Reach · error · RuntimeError
curl is unavailable for the V2EX TLS fallback
Error message
curl is unavailable for the V2EX TLS fallback
What it means
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.
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
Example fix
# before: slim Dockerfile with no curl FROM python:3.12-slim RUN pip install agent-reach # after: install curl so the V2EX TLS fallback works FROM python:3.12-slim RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* RUN pip install agent-reach
Defensive patterns
Strategy: validation
Validate before calling
import shutil
def v2ex_fallback_available() -> bool:
"""True when the curl TLS fallback can run on this host."""
return shutil.which("curl") is not None
# before calling V2EXChannel
if not v2ex_fallback_available():
raise SystemExit("install curl, or route V2EX reads through your own HTTP client") Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- curl could not complete the V2EX TLS fallback
- invalid V2EX API URL
- only the V2EX HTTPS API is allowed
- V2EX API response exceeds the 1 MiB safety limit
- not found in PATH
AI-assisted analysis of Panniantong/Agent-Reach@93ae1d18c3 (2026-08-14).
Data as JSON: /api/errors/7cd7c77fd65fb668.
Report an issue: GitHub.
Appendix: source
Thrown at agent_reach/channels/v2ex.py:90
or "eof occurred in violation of protocol" in text
):
return True
for nested in (
getattr(current, "reason", None),
current.__cause__,
current.__context__,
):
if isinstance(nested, BaseException):
pending.append(nested)
return False
def _get_json_with_curl(url: str) -> Any:
"""Fetch bounded JSON with the OS curl TLS stack."""
_validate_api_url(url)
curl = shutil.which("curl")
if not curl:
raise RuntimeError("curl is unavailable for the V2EX TLS fallback")
command = [
curl,
"--fail",
"--silent",
"--show-error",
"--proto",
"=https",
"--connect-timeout",
"5",
"--max-time",
str(_TIMEOUT),
"--max-filesize",
str(_MAX_RESPONSE_BYTES),
"--header",
f"User-Agent: {_UA}",
"--url",
url,View on GitHub (pinned to 93ae1d18c3)