{"record":{"id":"1b0825382cf6fe04","repo":"fish2018/pansou","slug":"susu-w-1b0825","errorCode":null,"errorMessage":"[susu] 读取按钮列表失败: %w","messagePattern":"\\[susu\\] 读取按钮列表失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/susu/susu.go","lineNumber":364,"sourceCode":"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, ButtonListURL, strings.NewReader(form.Encode()))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[susu] 创建按钮列表请求失败: %w\", err)\n\t}\n\tsetAPIHeaders(req, fmt.Sprintf(\"%s/%s.html\", BaseURL, postID))\n\n\tresp, err := p.doRequestWithRetry(client, req, MaxRetries)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[susu] 获取按钮列表失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"[susu] 按钮列表请求返回状态码: %d\", resp.StatusCode)\n\t}\n\n\trespBody, err := io.ReadAll(io.LimitReader(resp.Body, 2<<20))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[susu] 读取按钮列表失败: %w\", err)\n\t}\n\n\tvar groups []downloadGroup\n\tif err := json.Unmarshal(respBody, &groups); err != nil {\n\t\treturn nil, fmt.Errorf(\"[susu] 解析按钮列表失败: %w\", err)\n\t}\n\n\ttotalButtons := 0\n\tfor _, group := range groups {\n\t\ttotalButtons += len(group.Button)\n\t}\n\tif totalButtons == 0 {\n\t\treturn nil, fmt.Errorf(\"[susu] 帖子 %s 没有可用下载按钮\", postID)\n\t}\n\n\tlinkChan := make(chan model.Link, totalButtons)\n\tvar wgLinks sync.WaitGroup\n\tsemaphore := make(chan struct{}, MaxConcurrency)","sourceCodeStart":346,"sourceCodeEnd":382,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/susu/susu.go#L346-L382","documentation":"getLinks throws this when io.ReadAll fails while reading the (2 MiB-capped) button-list response body. This happens when the connection breaks mid-body, the context deadline fires during the read, or the body reader errors. The 2<<20 LimitReader means anything beyond 2 MiB is silently truncated, not errored — this error is about read failures, not size.","triggerScenarios":"io.ReadAll(io.LimitReader(resp.Body, 2<<20)) returns err after a 200 response — connection reset mid-body, ctx (20s) deadline exceeded while streaming, or a proxy aborting the transfer.","commonSituations":"Very slow API responses exceeding the 20s timeout mid-download; flaky mobile/proxied networks; server closing connections under load before the body completes.","solutions":["Unwrap the error: 'context deadline exceeded' means raise the 20s ctx timeout.","Retry the request — mid-body resets are often transient.","Increase doRequestWithRetry retries to cover body-read failures.","Check network stability / proxy configuration between host and API.","If responses legitimately exceed 2 MiB, note LimitReader truncates (it won't cause this error, but may cause [707] parse failures)."],"exampleFix":"// before\nrespBody, err := io.ReadAll(io.LimitReader(resp.Body, 2<<20))\nif err != nil {\n    return nil, fmt.Errorf(\"[susu] 读取按钮列表失败: %w\", err)\n}\n// after: include context cause and extend timeout\nctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\nrespBody, err := io.ReadAll(io.LimitReader(resp.Body, 2<<20))\nif err != nil {\n    return nil, fmt.Errorf(\"[susu] 读取按钮列表失败: %w\", err) // check for context.DeadlineExceeded\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"links, err := p.getLinks(postID)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, io.ErrUnexpectedEOF) {\n        // transient body read failure — retry with longer timeout\n    }\n}","preventionTips":["Allow enough context timeout for slow bodies","Retry mid-body read failures","Avoid unstable proxies between host and API","Watch for repeated ErrUnexpectedEOF indicating server-side connection drops"],"tags":["io","response-body","timeout","network"],"backgroundTag":"file-read-failed","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"}