sipeed/picoclaw · error
invalid proxy URL %q: %w
Error message
invalid proxy URL %q: %w
What it means
NewApiClient for the Weixin iLink API rejects a configured proxy string because url.Parse fails on it. Go's url.Parse almost never errors — it fails on control characters, an embedded NUL, or a malformed percent-escape — so this fires only for genuinely malformed proxy strings, not for a wrong-but-well-formed host. The client refuses to build rather than silently ignoring the proxy.
Source
Thrown at pkg/channels/weixin/api.go:43
type ApiClient struct {
BaseURL string
Token string
HttpClient *http.Client
}
func NewApiClient(baseURL, token string, proxy string) (*ApiClient, error) {
if baseURL == "" {
baseURL = "https://ilinkai.weixin.qq.com/"
}
client := &http.Client{
// Default timeout; will be overridden per context
}
if proxy != "" {
proxyURL, err := url.Parse(proxy)
if err != nil {
return nil, fmt.Errorf("invalid proxy URL %q: %w", proxy, err)
}
// Clone the default transport so we preserve all default settings (TLS, HTTP/2, timeouts, keep-alives)
if defaultTransport, ok := http.DefaultTransport.(*http.Transport); ok {
transport := defaultTransport.Clone()
transport.Proxy = http.ProxyURL(proxyURL)
client.Transport = transport
} else {
// Fallback: preserve previous behavior if DefaultTransport is not the expected type
client.Transport = &http.Transport{
Proxy: http.ProxyURL(proxyURL),
}
}
}
return &ApiClient{
BaseURL: baseURL,
Token: token,View on GitHub (pinned to 49183d7e8d)
Solutions
- Print the proxy string with %q to expose hidden control characters/newlines, then strip or fix them
- Percent-encode credentials in the proxy URL correctly (a literal % must be %25)
- Validate the proxy URL early in config loading with url.Parse and fail with a clear message at startup
- If no proxy is intended, leave the setting empty instead of a placeholder like "none"
Example fix
# before proxy: "http://user:pa%ss@proxy.local:8080" # invalid % escape -> error 666 # after proxy: "http://user:pa%25ss@proxy.local:8080" # % encoded as %25
Defensive patterns
Strategy: validation
Validate before calling
func validateProxy(proxy string) error {
if proxy == "" {
return nil
}
u, err := url.Parse(proxy)
if err != nil {
return fmt.Errorf("proxy %q unparseable: %w", proxy, err)
}
if u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "socks5" {
return fmt.Errorf("proxy scheme %q unsupported", u.Scheme)
}
return nil
}
if err := validateProxy(cfg.Proxy); err != nil { return err } Type guard
func isInvalidProxyURL(err error) bool {
return err != nil && strings.Contains(err.Error(), "invalid proxy URL")
} Try / catch
client, err := weixin.NewApiClient(baseURL, token, proxy)
if err != nil {
if isInvalidProxyURL(err) {
// config bug: fix/strip the proxy string, never retry
}
} Prevention
- Validate proxy URLs at config load, not at first API call
- Percent-encode credentials in proxy URLs (% as %25)
- Beware Windows line endings injecting \r into env values
- Use %q formatting to expose control characters when debugging
When it happens
Trigger: Constructing the Weixin API client with a non-empty proxy setting containing control characters, a raw newline/NUL, or an invalid percent-encoding (e.g. "http://proxy:80%zz" or a value with trailing CR from a config file). Well-formed-but-unreachable proxies do NOT trigger this; they fail later at request time.
Common situations: A .env or YAML value with an unescaped newline or trailing \r (Windows line endings); a percent sign in the proxy password that is not double-encoded; copy-pasting a proxy URL with invisible characters; a DNS name with a stray control byte.
Related errors
- failed to create api client: %w
- failed to load config: %w
- invalid discord proxy URL %q: %w
- ${label} must be a JSON object.
- ${label}.${key} must be a string.
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/20cfaf003ab3af6d.
Report an issue: GitHub.