Panniantong/Agent-Reach · error · ValueError
invalid V2EX API URL
Error message
invalid V2EX API URL
What it means
Raised by _validate_api_url() in the V2EX channel when urllib.parse.urlsplit() itself raises ValueError while parsing, or when accessing parsed.port fails — i.e. the URL string is structurally malformed (bad port syntax like 'https://v2ex.com:notaport/api/...', unmatched brackets, invalid IPv6). It is the parse-level guard before the stricter allowlist check (error 18).
Source
Thrown at agent_reach/channels/v2ex.py:34
_UA = "agent-reach/1.0"
_TIMEOUT = 10
_MAX_RESPONSE_BYTES = 1024 * 1024
_API_BASE = "https://www.v2ex.com"
def _v2ex_url(path: str, **params: Any) -> str:
"""Build a V2EX URL without letting caller values alter its query."""
return f"{_API_BASE}{path}?{urlencode(params)}"
def _validate_api_url(url: str) -> None:
"""Allow only the public V2EX HTTPS JSON API."""
try:
parsed = urlsplit(url)
port = parsed.port
except ValueError as exc:
raise ValueError("invalid V2EX API URL") from exc
if (
parsed.scheme.lower() != "https"
or (parsed.hostname or "").lower() not in {"v2ex.com", "www.v2ex.com"}
or port not in {None, 443}
or parsed.username is not None
or parsed.password is not None
or not parsed.path.startswith("/api/")
):
raise ValueError("only the V2EX HTTPS API is allowed")
def _get_json_with_urllib(url: str) -> Any:
"""Fetch JSON with Python's standard HTTP stack."""
_validate_api_url(url)
req = urllib.request.Request(url, headers={"User-Agent": _UA})
with urllib.request.urlopen(req, timeout=_TIMEOUT) as resp:
raw = resp.read(_MAX_RESPONSE_BYTES + 1)
if len(raw) > _MAX_RESPONSE_BYTES:View on GitHub (pinned to 93ae1d18c3)
Solutions
- Fix or discard the malformed URL — check the port segment syntax (https://host:443/api/... or omit it)
- Use the channel's own URL builder (_v2ex_url(path, **params)) instead of hand-building URLs
- If taking external input, validate with urlsplit() in a try/except before calling the channel
Example fix
# before
_get_json_with_urllib("https://v2ex.com:99x/api/topics/show.json")
# after
_get_json_with_urllib("https://v2ex.com/api/topics/show.json") Defensive patterns
Strategy: validation
Validate before calling
from urllib.parse import urlsplit
def url_parseable(url: str) -> bool:
try:
urlsplit(url).port
return True
except ValueError:
return False Type guard
def is_parseable_url(url) -> bool:
if not isinstance(url, str):
return False
try:
urlsplit(url).port
except ValueError:
return False
return True Try / catch
try:
_get_json_with_urllib(url)
except ValueError as exc:
if str(exc) == "invalid V2EX API URL":
reject_input_url(url) # caller-supplied URL was malformed Prevention
- Never hand-build V2EX URLs; use the channel's builder (_v2ex_url)
- Validate external URLs with urlsplit().port before passing them in
- Omit the port entirely for the default 443
When it happens
Trigger: Calling _get_json_with_urllib() (or any V2EX read/search that builds a custom URL) with a URL whose port component is non-numeric or out of syntax, or otherwise unparseable. Note: URLs built by _v2ex_url() never trigger this; only caller-supplied URLs can.
Common situations: Passing a user-provided or LLM-constructed URL into the channel's fetch path; string interpolation adding ':8443:' or leaving ':port' empty; URLs copy-pasted with invisible Unicode characters.
Related errors
- only the V2EX HTTPS API is allowed
- V2EX API response exceeds the 1 MiB safety limit
- curl is unavailable for the V2EX TLS fallback
- curl could not complete the V2EX TLS fallback
- Agent Reach 的 GitHub 配置无法读取
AI-assisted analysis of Panniantong/Agent-Reach@93ae1d18c3 (2026-08-14).
Data as JSON: /api/errors/6ed988428849f706.
Report an issue: GitHub.