{"record":{"id":"76deff6aca87f486","repo":"fish2018/pansou","slug":"s-w-76deff","errorCode":null,"errorMessage":"[%s] 读取流式结果失败: %w","messagePattern":"\\[(.+?)\\] 读取流式结果失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/jupansou/jupansou.go","lineNumber":153,"sourceCode":"\t\tif payload == \"\" || payload == \"[DONE]\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar item juPansouStreamItem\n\t\tif err := json.Unmarshal([]byte(payload), &item); err != nil {\n\t\t\tcontinue\n\t\t}\n\t\titem.Title = strings.TrimSpace(item.Title)\n\t\titem.Name = strings.TrimSpace(item.Name)\n\t\titem.URL = strings.TrimSpace(item.URL)\n\t\tif item.Title == \"\" || item.URL == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems = append(items, item)\n\t}\n\n\tif err := scanner.Err(); err != nil && len(items) == 0 {\n\t\treturn nil, fmt.Errorf(\"[%s] 读取流式结果失败: %w\", p.Name(), err)\n\t}\n\n\t// The stream may contain broad third-party lines. Filter by title before\n\t// exchanging encrypted URLs to avoid unnecessary transfer requests.\n\tkeywordLower := strings.ToLower(strings.TrimSpace(keyword))\n\tfilteredItems := items[:0]\n\tfor _, item := range items {\n\t\tif keywordLower == \"\" || strings.Contains(strings.ToLower(item.Title), keywordLower) {\n\t\t\tfilteredItems = append(filteredItems, item)\n\t\t}\n\t}\n\n\tresults := p.exchangeItems(client, filteredItems)\n\treturn plugin.FilterResultsByKeyword(results, keyword), nil\n}\n\nfunc (p *JuPansouPlugin) ensureSearchSession(client *http.Client) error {\n\tctx, cancel := context.WithTimeout(context.Background(), jupansouTimeout)","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/jupansou/jupansou.go#L135-L171","documentation":"After scanning the SSE/streaming response line by line, searchImpl checks scanner.Err(). If the stream read failed (connection reset mid-stream, chunk decode error, line exceeding buffer) AND no items were collected, this error is returned. If some items were already parsed, the error is swallowed in favor of partial results.","triggerScenarios":"bufio.Scanner errors while reading the streaming body — connection dropped by the server mid-stream, or a single line exceeding the 1MB scanner buffer.","commonSituations":"Upstream closes the connection abruptly under load; a proxy/CDN truncates the SSE stream; extremely large response lines exceeding the 1MB max token size.","solutions":["Unwrap the error to distinguish connection reset from buffer overflow (`bufio.Scanner: token too long`).","If token too long, raise the scanner buffer: scanner.Buffer(make([]byte, 0, 64*1024), largerMax).","Retry the search — a mid-stream reset is often transient.","Check intermediary proxies/CDN timeouts that may cut long-lived streaming responses."],"exampleFix":"// before: fixed 1MB cap\nscanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)\n// after: larger max line for big payloads\nscanner.Buffer(make([]byte, 0, 64*1024), 8*1024*1024)","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"results, err := p.searchImpl(client, keyword)\nif err != nil && strings.Contains(err.Error(), \"读取流式结果失败\") {\n    log.Printf(\"stream truncated; returning cached results: %v\", err)\n    return cache.Get(keyword)\n}","preventionTips":["Size the scanner buffer above the largest expected line (raise the 1MB cap if payloads can be big).","Treat partial stream results as acceptable and return them instead of failing.","Watch for proxies/CDNs with short idle timeouts that cut long SSE streams.","Retry once automatically on mid-stream resets."],"tags":["network","streaming","bufio","sse"],"backgroundTag":"network-request-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"}