Tencent/WeKnora · error

yuque api error: status=%d body=%s

Error message

yuque api error: status=%d body=%s

What it means

Same branch as the msg variant: doRequest gets a non-2xx status (outside 401/403/429/5xx) but the body either isn't JSON or has no parseable "message" field, so the client falls back to embedding a truncated (500-char) raw body in the error. This is the diagnostic fallback that tells you Yuque returned an unrecognized error payload. It is returned un-retried to every caller of doRequest.

Source

Thrown at internal/datasource/connector/yuque/client.go:140

				}
				continue
			}
			return lastErr
		}

		// 401/403 → surface as ErrInvalidCredentials so DataSourceService can
		// distinguish bad-token from transient failures and auto-flag the source.
		if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
			return fmt.Errorf("%w: status=%d body=%s", datasource.ErrInvalidCredentials, resp.StatusCode, bodyPreview)
		}

		if resp.StatusCode < 200 || resp.StatusCode >= 300 {
			var apiErr apiErrorBody
			_ = json.Unmarshal(body, &apiErr)
			if apiErr.Message != "" {
				return fmt.Errorf("yuque api error: status=%d msg=%s", resp.StatusCode, apiErr.Message)
			}
			return fmt.Errorf("yuque api error: status=%d body=%s", resp.StatusCode, bodyPreview)
		}

		if result != nil {
			if err := json.Unmarshal(body, result); err != nil {
				return fmt.Errorf("decode response: %w", err)
			}
		}
		return nil
	}
	return lastErr
}

// parseRetryAfter returns the Retry-After duration from the header, or fallback if unparseable.
// Retry-After: "0" (or negative) is coerced to 100ms so we still yield and don't busy-retry.
// Note: only integer-seconds form is supported (RFC 7231 also allows HTTP-date — not seen from Yuque).
func parseRetryAfter(header string, fallback time.Duration) time.Duration {
	if header == "" {
		return fallback

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Inspect the body= content in the error — it identifies who actually produced the response (HTML → proxy/nginx; JSON with a different shape → API version mismatch).
  2. If the body is an HTML block page, fix the network path: check proxy env vars (HTTP_PROXY/HTTPS_PROXY), corporate egress rules, or the Yuque instance's fronting nginx config.
  3. Verify baseURL is exactly the API root (e.g. https://www.yuque.com/api/v2 or the self-hosted equivalent); wrong roots return non-JSON 404s.
  4. Capture the full response with curl -i to see headers and confirm which server answered.
  5. If the body shows a message under a non-standard key, extend apiErrorBody to match your Yuque version's error format.

Example fix

// before
baseURL: "https://www.yuque.com" // page host, API paths then 404 with HTML
// after
baseURL: "https://www.yuque.com/api/v2" // or set api_base_url in the datasource config
Defensive patterns

Strategy: type-guard

Validate before calling

// Go: probe the base URL for a JSON-capable API before configuring
resp, err := http.Get(baseURL + "/api/v2/user")
if err == nil {
    ct := resp.Header.Get("Content-Type")
    if !strings.Contains(ct, "application/json") {
        // a proxy/html responder sits in front — fix network path or base URL
    }
}

Type guard

// Go: detect the raw-body fallback variant
func isYuqueRawBodyError(err error) (bodySnippet string, ok bool) {
    if err == nil {
        return "", false
    }
    if strings.HasPrefix(err.Error(), "yuque api error: status=") && strings.Contains(err.Error(), "body=") {
        i := strings.Index(err.Error(), "body=")
        return err.Error()[i+5:], true
    }
    return "", false
}

Try / catch

err := cli.Ping(ctx)
if err != nil {
    if body, ok := isYuqueRawBodyError(err); ok {
        if strings.Contains(strings.ToLower(body), "<html") {
            return fmt.Errorf("a proxy/firewall intercepted the Yuque request, check network egress: %s", body)
        }
    }
    return err
}

Prevention

When it happens

Trigger: A non-2xx response whose body lacks the {"message":"..."} shape: an HTML error page from a proxy/reverse-proxy/gateway, an empty body, or a Yuque error format the apiErrorBody struct doesn't match (e.g. message nested under a different key), for any endpoint from Ping through GetDocDetail.

Common situations: A corporate proxy or WAF intercepts the request and returns an HTML 403/400 block page; a self-hosted Yuque (or its nginx front) emits non-JSON error pages; a typo'd baseURL (e.g. missing path prefix) yields a non-JSON 404; an API gateway rate-limits or errors with a custom payload.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/7a8df28795a3f0c6. Report an issue: GitHub.