{"record":{"id":"bdc9aa3624b6c6ac","repo":"chenhg5/cc-connect","slug":"s-cdn-upload-client-error-d-s","errorCode":null,"errorMessage":"%s: CDN upload client error %d: %s","messagePattern":"(.+?): CDN upload client error (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/weixin/cdn.go","lineNumber":203,"sourceCode":"\t\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, u, bytes.NewReader(ciphertext))\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"%s: new request: %w\", label, err)\n\t\t}\n\t\treq.Header.Set(\"Content-Type\", \"application/octet-stream\")\n\t\tresp, err := client.Do(req)\n\t\tif err != nil {\n\t\t\tlastErr = err\n\t\t\tslog.Warn(\"weixin: CDN upload request failed\", \"label\", label, \"attempt\", attempt, \"error\", err)\n\t\t\tcontinue\n\t\t}\n\t\t_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 4096))\n\t\t_ = resp.Body.Close()\n\t\tif resp.StatusCode >= 400 && resp.StatusCode < 500 {\n\t\t\tmsg := resp.Header.Get(\"x-error-message\")\n\t\t\tif msg == \"\" {\n\t\t\t\tmsg = resp.Status\n\t\t\t}\n\t\t\treturn \"\", fmt.Errorf(\"%s: CDN upload client error %d: %s\", label, resp.StatusCode, msg)\n\t\t}\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\tmsg := resp.Header.Get(\"x-error-message\")\n\t\t\tif msg == \"\" {\n\t\t\t\tmsg = fmt.Sprintf(\"status %d\", resp.StatusCode)\n\t\t\t}\n\t\t\tlastErr = fmt.Errorf(\"%s: CDN upload server error: %s\", label, msg)\n\t\t\tslog.Warn(\"weixin: CDN upload server error\", \"label\", label, \"attempt\", attempt, \"error\", lastErr)\n\t\t\tcontinue\n\t\t}\n\t\tdl := resp.Header.Get(\"x-encrypted-param\")\n\t\tif dl == \"\" {\n\t\t\tlastErr = fmt.Errorf(\"%s: CDN response missing x-encrypted-param\", label)\n\t\t\tslog.Warn(\"weixin: CDN upload bad response\", \"label\", label, \"attempt\", attempt)\n\t\t\tcontinue\n\t\t}\n\t\treturn dl, nil\n\t}","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/weixin/cdn.go#L185-L221","documentation":"uploadBufferToCDN rejects the upload when the WeChat CDN returns an HTTP 4xx client error. The CDN signals the reason via the x-error-message header (falling back to resp.Status). Client errors are NOT retried because retrying a malformed/rejected request cannot succeed.","triggerScenarios":"POST of AES-ECB ciphertext to the CDN upload URL returns status 400-499; typical causes are an expired/invalid encrypted_query_param or filekey from getUploadUrl, a wrong URL, or the CDN rejecting content metadata.","commonSituations":"Stale upload URL reused after expiry; filekey mismatch between getUploadUrl and the upload request; CDN base URL misconfigured in config.toml; CDN-side rejecting oversize or malformed payloads with 4xx.","solutions":["Inspect the %s (msg) portion of the error for the CDN's x-error-message to identify the rejection reason","Call getUploadUrl again to get a fresh upload_param/upload_full_url and retry with the new URL","Verify cdn_base (default https://novac2c.cdn.weixin.qq.com/c2c) is correct in config.toml","Check that the aes_key passed to uploadBufferToCDN is exactly 16 bytes and that the filekey matches the getUploadUrl response"],"exampleFix":"// before: blind retry on any error\nfor i := 0; i < 3; i++ { _, err := uploadToWeixinCDN(ctx, ...); if err == nil { break } }\n// after: refresh the upload URL only on client-error and retry once\nif err != nil && strings.Contains(err.Error(), \"CDN upload client error\") {\n    resp, err2 := client.getUploadURL(ctx, freshGetUploadURLRequest)\n    if err2 == nil { /* rebuild URL and retry once */ }\n}","handlingStrategy":"validation","validationCode":"// before uploading, ensure the upload URL and key are fresh and well-formed\nif uploadParam == \"\" || len(aesKey) != 16 || time.Since(uploadURLIssuedAt) > 5*time.Minute {\n    // re-call getUploadUrl and re-derive aesKey\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always fetch a fresh upload URL from getUploadUrl immediately before each upload","Never cache upload_param/upload_full_url across sessions or long periods","Validate aes_key is 16 raw bytes before uploading","Log the x-error-message from failures to spot contract drift early"],"tags":["http","cdn","upload","weixin"],"backgroundTag":"http-error-response","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}