{"record":{"id":"2fbf8894596333a7","repo":"fish2018/pansou","slug":"http-d-2fbf88","errorCode":null,"errorMessage":"HTTP 状态码 %d","messagePattern":"HTTP 状态码 (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/cyg/cyg.go","lineNumber":395,"sourceCode":"\t\t\t// 指数退避重试\n\t\t\tbackoff := time.Duration(1<<uint(i-1)) * 200 * time.Millisecond\n\t\t\ttime.Sleep(backoff)\n\t\t}\n\n\t\t// 克隆请求避免并发问题\n\t\treqClone := req.Clone(req.Context())\n\n\t\tresp, err := client.Do(reqClone)\n\t\tif err == nil && resp.StatusCode == 200 {\n\t\t\treturn resp, nil\n\t\t}\n\n\t\tif resp != nil {\n\t\t\tresp.Body.Close()\n\t\t}\n\t\tlastErr = err\n\t\tif err == nil {\n\t\t\tlastErr = fmt.Errorf(\"HTTP 状态码 %d\", resp.StatusCode)\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"重试 %d 次后仍然失败: %w\", maxRetries, lastErr)\n}\n\n// parseExtOptions 从ext参数中解析搜索选项\nfunc (p *CygPlugin) parseExtOptions(ext map[string]interface{}) CygSearchOptions {\n\topts := CygSearchOptions{\n\t\tPerPage: 20,\n\t\tPage:    1,\n\t\tOrderBy: \"date\",\n\t\tOrder:   \"desc\",\n\t}\n\n\tif ext == nil {\n\t\treturn opts\n\t}","sourceCodeStart":377,"sourceCodeEnd":413,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/cyg/cyg.go#L377-L413","documentation":"doRequestWithRetry records a non-nil error from the HTTP attempt; when err == nil but the loop still ended (i.e., the response had a non-acceptable status), it synthesizes \"HTTP 状态码 %d\" from resp.StatusCode. This becomes the wrapped cause of the final \"重试 N 次后仍然失败\" error. Note the condition looks inverted at a glance but is correct: it fires only when the attempt returned a response (err == nil) rather than a transport error.","triggerScenarios":"All retry attempts in doRequestWithRetry return responses whose status codes are not accepted by the plugin's success check (e.g. non-200), so the last such status is stored as the sentinel error.","commonSituations":"Target site persistently returning 403 (anti-bot/WAF), 429 (rate limit) or 5xx across all retries; endpoint moved returning 404; required cookies/headers missing.","solutions":["Log each attempt's status code inside the retry loop to see the actual codes received.","If 403/429, slow down request rate, rotate headers/cookies, or extend backoff between retries.","If 404, verify the endpoint URL is still valid after a site update.","Consider treating 4xx as non-retryable to fail fast instead of burning all retries."],"exampleFix":"// before\nlastErr = err\nif err == nil {\n    lastErr = fmt.Errorf(\"HTTP 状态码 %d\", resp.StatusCode)\n}\n// after\nif err != nil {\n    lastErr = err\n} else {\n    lastErr = fmt.Errorf(\"HTTP 状态码 %d\", resp.StatusCode)\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"resp, err := p.doRequestWithRetry(req, client)\nif err != nil {\n    if strings.Contains(err.Error(), \"HTTP 状态码\") {\n        // server kept returning a bad status; do not retry immediately\n        return nil, fmt.Errorf(\"endpoint unhealthy: %w\", err)\n    }\n    return nil, err\n}","preventionTips":["Log each attempt's status code to distinguish persistent blocks from transient 5xx.","Back off longer between retries when statuses are 403/429.","Fail fast on 4xx instead of consuming all retries."],"tags":["http","retry","go","status-code"],"backgroundTag":"http-non-200-response","analyzedSha":"beaa56133755a548ebc51b090b3816e2ae044aa6","analyzedAt":"2026-09-07T00:31:18.025Z","contentChangedAt":"2026-09-07T00:31:18.025Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}