{"record":{"id":"07a704d86eab984e","repo":"mvanhorn/last30days-skill","slug":"flag-name-requires-at-least-one-source","errorCode":null,"errorMessage":"{flag_name} requires at least one source.","messagePattern":"(.+?) requires at least one source\\.","errorType":"validation","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/last30days.py","lineNumber":98,"sourceCode":"            continue\n\n\natexit.register(_cleanup_children)\n\n\ndef parse_search_flag(raw: str, flag_name: str = \"--search\") -> list[str]:\n    sources = []\n    for source in raw.split(\",\"):\n        source = source.strip().lower()\n        if not source:\n            continue\n        normalized = pipeline.SEARCH_ALIAS.get(source, source)\n        if normalized not in pipeline.MOCK_AVAILABLE_SOURCES:\n            raise SystemExit(f\"Unknown search source in {flag_name}: {source}\")\n        if normalized not in sources:\n            sources.append(normalized)\n    if not sources:\n        raise SystemExit(f\"{flag_name} requires at least one source.\")\n    return sources\n\ndef parse_as_of_date_arg(value: str) -> str:\n    try:\n        parsed = dates.parse_as_of_date(value)\n    except ValueError as exc:\n        raise argparse.ArgumentTypeError(str(exc)) from exc\n    return parsed\n\ndef resolve_requested_sources(args_search: str | None, config: dict) -> list[str] | None:\n    \"\"\"Resolve the requested source set: explicit --search wins, then the\n    LAST30DAYS_DEFAULT_SEARCH config key (env var or .env file), then None\n    (per-query default behavior). The config fallback lets users pin a fixed\n    source set that survives upgrades without patching SKILL.md (#442).\n    \"\"\"\n    if args_search:\n        return parse_search_flag(args_search)\n    default_search = (config.get(\"LAST30DAYS_DEFAULT_SEARCH\") or \"\").strip()","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/last30days.py#L80-L116","documentation":"parse_search_flag() raises SystemExit when, after splitting the raw --search value on commas and dropping empty/whitespace-only tokens, zero valid sources remain — i.e. the flag was passed but effectively empty.","triggerScenarios":"--search '' , --search ',' , or --search ' , , ' — every comma-separated token is blank, so the sources list stays empty and the guard fires.","commonSituations":"Shell quoting bugs where an unset variable expands to empty string ($SEARCH_SOURCES unset); agent scripts templating the flag from a config key that is blank; trailing-comma lists built by string join over an empty list.","solutions":["Pass at least one real source name: --search reddit or --search reddit,x.","If the value comes from a variable, guard in the caller: only append the flag when the variable is non-empty.","Check for stray quotes: --search \"\" is an empty request, not 'all sources'."],"exampleFix":"# before\ncmd = ['python3', 'last30days.py', topic, '--search', sources_var]\n\n# after\ncmd = ['python3', 'last30days.py', topic]\nif sources_var:\n    cmd += ['--search', sources_var]","handlingStrategy":"validation","validationCode":"tokens = [t.strip().lower() for t in raw.split(',') if t.strip()]\nif not tokens:\n    raise ValueError('--search value resolves to zero sources; omit the flag or name a source')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only append --search to command lists when the value variable is non-empty.","Treat an empty sources variable as 'omit flag' in template code, never as an empty string argument."],"tags":["cli","validation","arguments","sources"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}