{"record":{"id":"f2bcb6b692a1dd0f","repo":"mvanhorn/last30days-skill","slug":"no-listing-sources-are-available-for-normalized-d","errorCode":null,"errorMessage":"No listing sources are available for {normalized_domain!r}","messagePattern":"No listing sources are available for (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/lib/planner.py","lineNumber":76,"sourceCode":"    seen_subreddits: set[str] = set()\n    resolved_subreddits: list[str] = []\n    for subreddit in candidate_subreddits:\n        normalized_subreddit = subreddit.removeprefix(\"r/\").strip()\n        key = normalized_subreddit.lower()\n        if not normalized_subreddit or key in seen_subreddits:\n            continue\n        seen_subreddits.add(key)\n        resolved_subreddits.append(normalized_subreddit)\n    # The curated map intentionally stays small. Keep discovery's keyless floor\n    # for uncategorized domains by sweeping r/all and applying domain relevance\n    # during normalization instead of inventing a second category resolver.\n    if not resolved_subreddits:\n        resolved_subreddits = [\"all\"]\n\n    allowed = set(DISCOVERY_SOURCE_ORDER if available_sources is None else available_sources)\n    sources = [source for source in DISCOVERY_SOURCE_ORDER if source in allowed]\n    if not sources:\n        raise ValueError(f\"No listing sources are available for {normalized_domain!r}\")\n\n    return schema.DiscoveryPlan(\n        domain=normalized_domain,\n        category=category,\n        subreddits=resolved_subreddits,\n        sources=sources,\n    )\n\nALLOWED_INTENTS = {\n    \"factual\",\n    \"product\",\n    \"concept\",\n    \"opinion\",\n    \"how_to\",\n    \"comparison\",\n    \"breaking_news\",\n    \"prediction\",\n}","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/planner.py#L58-L94","documentation":"Same guard as the global branch, but for a non-empty domain: build_discovery_plan intersects DISCOVERY_SOURCE_ORDER with available_sources and raises when the intersection is empty. Unlike the global branch, 'x' is NOT discarded here, but 'x' is also not in DISCOVERY_SOURCE_ORDER, so passing only non-listing sources still triggers it. The domain is echoed with !r for easy debugging.","triggerScenarios":"build_discovery_plan(domain='example topic', available_sources=['youtube']) or available_sources=[] — the ordered intersection [s for s in DISCOVERY_SOURCE_ORDER if s in allowed] is empty and the ValueError fires with the domain repr.","commonSituations":"Custom callers/tests passing restricted or empty available_sources; configs where every listing source was filtered out upstream (e.g. all failed availability checks); passing search-style sources to the discovery planner.","solutions":["Pass at least one listing source (reddit/hackernews/digg) in available_sources.","Use run_discover instead of the planner directly — it computes availability and filters requested sources correctly.","If this surfaces from the pipeline path, treat it as 'no listing source currently available' and fix credentials/PATH for digg if that was the only one requested."],"exampleFix":"# before\nplan = planner.build_discovery_plan(\"rust compilers\", available_sources=[])\n\n# after\nplan = planner.build_discovery_plan(\"rust compilers\", available_sources=[\"reddit\", \"hackernews\", \"digg\"])","handlingStrategy":"validation","validationCode":"LISTING = {\"reddit\", \"hackernews\", \"digg\"}\nif not (set(available_sources or []) & LISTING):\n    available_sources = list(LISTING)  # or raise explicitly\nplan = planner.build_discovery_plan(domain, available_sources=available_sources)","typeGuard":null,"tryCatchPattern":"try:\n    plan = planner.build_discovery_plan(domain, available_sources=srcs)\nexcept ValueError as exc:\n    if \"No listing sources\" in str(exc):\n        plan = planner.build_discovery_plan(domain, available_sources=[\"reddit\"])\n    else:\n        raise","preventionTips":["Prefer run_discovery over direct planner calls; it computes availability for you.","All three listing sources work without keys, so an empty intersection almost always means a caller bug, not missing credentials."],"tags":["planner","discovery","sources","validation"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}