{"record":{"id":"10a8779cbbb35630","repo":"mvanhorn/last30days-skill","slug":"discovery-supports-listing-sources-only-reddit-h","errorCode":null,"errorMessage":"Discovery supports listing sources only: reddit, hackernews, digg (unsupported: {', '.join(unsupported)})","messagePattern":"Discovery supports listing sources only: reddit, hackernews, digg \\(unsupported: (.+?)\\)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/lib/pipeline.py","lineNumber":1062,"sourceCode":"    config: dict[str, Any],\n    depth: str,\n    requested_sources: list[str] | None,\n    mock: bool,\n    subreddits: list[str] | None,\n    lookback_days: int,\n    as_of_date: str | None,\n) -> _DiscoverySweep:\n    \"\"\"Resolve the momentum window, validate/bound the listing sources, build\n    the discovery plan, sweep the river feeds, and finalize source status.\n\n    Shared verbatim by ``run_discover`` (one-shot) and\n    ``run_discover_nominate`` (protocol leg 1) so the two paths can never\n    drift on what a sweep means.\"\"\"\n    from_date, to_date = dates.get_date_range(lookback_days, as_of_date=as_of_date)\n    requested = normalize_requested_sources(requested_sources)\n    unsupported = sorted(set(requested or []) - set(DISCOVERY_SOURCES))\n    if unsupported:\n        raise ValueError(\n            \"Discovery supports listing sources only: reddit, hackernews, digg \"\n            f\"(unsupported: {', '.join(unsupported)})\"\n        )\n    available = list(DISCOVERY_SOURCES) if mock else [\n        source for source in available_sources(config, requested, x_pending=False)\n        if source in DISCOVERY_SOURCES\n    ]\n    if requested:\n        available = [source for source in available if source in requested]\n    plan = planner.build_discovery_plan(\n        domain,\n        available_sources=available,\n        subreddits=subreddits,\n    )\n\n    global_mode = not plan.domain\n    domain_label = plan.domain or \"everything\"\n    query_plan = schema.QueryPlan(","sourceCodeStart":1044,"sourceCodeEnd":1080,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/pipeline.py#L1044-L1080","documentation":"Thrown by the shared discovery-sweep prologue in pipeline.py when the caller passes requested sources that are not listing sources. Discovery (run_discover / run_discover_nominate) only sweeps feed-style listing sources — reddit, hackernews, digg — because it works by listing recent feeds, not by keyword search. Any other source name (e.g. 'x', 'youtube', 'bluesky') is rejected up front before any network call.","triggerScenarios":"Calling run_discover / run_discover_nominate (or the CLI discover path) with requested_sources containing names outside DISCOVERY_SOURCES, e.g. ['reddit', 'x'] or ['youtube']. The set difference of requested minus {reddit, hackernews, digg} is non-empty after normalize_requested_sources().","commonSituations":"Reusing a research-run source list (--sources reddit,x,youtube) for a discovery run; assuming discovery accepts the same source vocabulary as the main pipeline; typos in source names; a harness passing INCLUDE_SOURCES-style env values into discovery.","solutions":["Restrict requested_sources for discovery calls to subsets of ['reddit', 'hackernews', 'digg'].","If you want X/YouTube content, use the main research pipeline (run_pipeline), not discovery — discovery only nominates topics from listing feeds.","Filter the offending names before calling: [s for s in requested if s in {'reddit','hackernews','digg'}].","Check for typos/casing in the source names (normalize_requested_sources handles aliases, but non-existent sources still fail)."],"exampleFix":"# before\nplan = run_discover(domain=\"ai agents\", requested_sources=[\"reddit\", \"x\", \"youtube\"])\n\n# after\nplan = run_discover(domain=\"ai agents\", requested_sources=[\"reddit\", \"hackernews\", \"digg\"])","handlingStrategy":"validation","validationCode":"DISCOVERY_SOURCES = {\"reddit\", \"hackernews\", \"digg\"}\nrequested = [\"reddit\", \"x\"]\nbad = set(requested) - DISCOVERY_SOURCES\nif bad:\n    raise SystemExit(f\"not valid for discovery: {sorted(bad)}\")\nrequested = [s for s in requested if s in DISCOVERY_SOURCES] or list(DISCOVERY_SOURCES)","typeGuard":"def is_discovery_source_list(sources: list[str]) -> bool:\n    return bool(sources) and all(s in {\"reddit\", \"hackernews\", \"digg\"} for s in sources)","tryCatchPattern":"try:\n    sweep = run_discover(domain, requested_sources=clean)\nexcept ValueError as exc:\n    if \"unsupported\" in str(exc):\n        clean = [s for s in clean if s in DISCOVERY_SOURCES]\n        sweep = run_discover(domain, requested_sources=clean)\n    else:\n        raise","preventionTips":["Keep a module-level DISCOVERY_SOURCES constant and validate against it before every discovery call.","Never forward main-pipeline source lists (which include x/youtube/etc.) into discovery unfiltered."],"tags":["discovery","validation","sources","input-validation"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}