fish2018/pansou · error

cloudscraper not initialized

Error message

cloudscraper not initialized

What it means

The Discourse async plugin's searchImpl returns this sentinel error when p.scraper is nil, i.e. the cloudscraper client required to make Cloudflare-resilient requests was never initialized. Unlike other plugins this message is a plain English string without the plugin name prefix.

Solutions

  1. Check startup logs for the cloudscraper initialization failure and fix it.
  2. Ensure the plugin's initialization that assigns p.scraper always runs before searching.
  3. Fail fast at registration time if the scraper cannot be created.
  4. If a plain http.Client suffices for non-Cloudflare instances, add a fallback path instead of erroring.

Example fix

// before
p := &DiscourseAsyncPlugin{}
results, err := p.Search(keyword) // error: cloudscraper not initialized
// after
p := NewDiscourseAsyncPlugin()
if err := p.Init(cfg); err != nil { log.Fatalf("init: %v", err) }
results, err := p.Search(keyword)
Defensive patterns

Strategy: validation

Validate before calling

if p.scraper == nil {
    return errors.New("discourse plugin not initialized: cloudscraper is nil")
}
// safe to search

Try / catch

if err != nil {
    if err.Error() == "cloudscraper not initialized" {
        return fmt.Errorf("run plugin Init before searching: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: searchImpl is called (via the async search path) before or without a successful scraper initialization: Init/constructor failure, plugin registered but never configured, or instantiation in tests bypassing setup.

Common situations: cloudscraper init error swallowed at startup; plugin config missing required options for scraper creation; unit tests constructing the struct literal without running setup.

Related errors


AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07). Data as JSON: /api/errors/11285b234858988a. Report an issue: GitHub.

Appendix: source

Thrown at plugin/discourse/discourse.go:166

func (p *DiscourseAsyncPlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
	result, err := p.SearchWithResult(keyword, ext)
	if err != nil {
		return nil, err
	}
	return result.Results, nil
}

// SearchWithResult 执行搜索并返回包含IsFinal标记的结果
func (p *DiscourseAsyncPlugin) SearchWithResult(keyword string, ext map[string]interface{}) (model.PluginSearchResult, error) {
	// 使用BaseAsyncPlugin的异步搜索能力
	return p.AsyncSearchWithResult(keyword, p.searchImpl, p.MainCacheKey, ext)
}

// searchImpl 实现具体的搜索逻辑
func (p *DiscourseAsyncPlugin) searchImpl(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
	// 检查 cloudscraper 是否初始化成功
	if p.scraper == nil {
		return nil, fmt.Errorf("cloudscraper not initialized")
	}

	// 提取 max_pages 参数(最多获取多少页)
	maxPages := defaultMaxPages
	if maxPagesVal, ok := ext["max_pages"]; ok {
		if maxPagesInt, ok := maxPagesVal.(int); ok {
			maxPages = maxPagesInt
		} else if maxPagesFloat, ok := maxPagesVal.(float64); ok {
			maxPages = int(maxPagesFloat)
		}
	}
	
	// 限制最大页数
	if maxPages > maxAllowedPages {
		maxPages = maxAllowedPages
	}
	if maxPages < 1 {
		maxPages = 1

View on GitHub (pinned to beaa561337)