sipeed/picoclaw · error

%s failed: %d %s

Error message

%s failed: %d %s

What it means

Generic non-2xx failure from the Weixin iLink HTTP layer: the request reached the server, the response was read in full, but the status code was not 200. The error embeds the endpoint name, the numeric status, and the entire response body, which for iLink APIs usually carries a JSON error code/message explaining the refusal. Note that business-level errors inside a 200 body are handled elsewhere (json.Unmarshal), so this is purely an HTTP-status failure.

Source

Thrown at pkg/channels/weixin/api.go:202

	req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
	if err != nil {
		return err
	}
	req.Header["iLink-App-Id"] = []string{weixinIlinkAppID}
	req.Header["iLink-App-ClientVersion"] = []string{strconv.Itoa(weixinClientVersion)}

	resp, err := c.HttpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("%s failed: %d %s", endpoint, resp.StatusCode, string(respBody))
	}
	if err := json.Unmarshal(respBody, respObj); err != nil {
		return err
	}

	return nil
}

func (c *ApiClient) GetQRCode(ctx context.Context, botType string) (*QRCodeResponse, error) {
	// get_bot_qrcode is GET, not POST
	var qrcodeResp QRCodeResponse
	if err := c.getQR(ctx, "ilink/bot/get_bot_qrcode", map[string]string{
		"bot_type": botType,
	}, &qrcodeResp); err != nil {
		return nil, err
	}
	return &qrcodeResp, nil
}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Read the embedded response body in the error text — the iLink errcode/message pinpoints the cause (auth vs rate-limit vs server)
  2. For 401/403, re-run the QR login flow to obtain a fresh bot_token
  3. For 429, back off and retry with exponential delay; reduce send rate
  4. For 5xx, retry after a pause; check Weixin service status
  5. For 404, verify BaseURL (default https://ilinkai.weixin.qq.com/) and that no path-rewriting proxy is in front

Example fix

// before: treat every error the same
if err := api.SendMessage(ctx, ...); err != nil { panic(err) }

// after: surface status and body
if err := api.SendMessage(ctx, ...); err != nil {
    log.Printf("weixin api failed: %v", err) // err already contains endpoint, status, body
}
Defensive patterns

Strategy: retry

Validate before calling

// health-check credentials and endpoint before a real send
func weixinAPIHealthy(ctx context.Context, api *weixin.ApiClient) error {
    // any cheap authenticated GET; non-200 surfaces here with status+body
    return api.Ping(ctx)
}

Type guard

type httpAPIError struct{ endpoint string; status int; body string }

func isHTTPStatusError(err error) (httpAPIError, bool) {
    // parse "%s failed: %d %s" produced by api.go:202
    if err == nil {
        return httpAPIError{}, false
    }
    var s string
    if _, e := fmt.Sscanf(err.Error(), "%s failed: %d", &s, new(int)); e != nil {
        return httpAPIError{}, false
    }
    return httpAPIError{endpoint: s}, true
}

Try / catch

if err := api.Do(ctx, ...); err != nil {
    if e, ok := isHTTPStatusError(err); ok {
        switch {
        case e.status >= 500 || e.status == 429:
            retryWithBackoff() // transient
        case e.status == 401 || e.status == 403:
            relogin() // credential problem
        default:
            logAndPark(err) // 4xx: read body for iLink errcode
        }
    }
}

Prevention

When it happens

Trigger: Any ApiClient POST/GET (e.g. ilink/bot/get_bot_qrcode, send message, upload media) returning 4xx/5xx: 401/403 from an invalid or expired bot token, 404 from a wrong BaseURL or API path, 429 from rate limiting, 5xx during server maintenance, or an HTML error page from a captive portal/proxy.

Common situations: The bot_token expired after the Weixin session was invalidated (every API call then 401s); BaseURL misconfigured or redirected; hitting rate limits during message bursts; transient Weixin service instability; a proxy returning 407 because credentials were never supplied.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/813ba868444f2860. Report an issue: GitHub.