{"record":{"id":"554afcda545e88e6","repo":"github/spec-kit","slug":"providers-i-provider-must-be-a-non-empty-str","errorCode":null,"errorMessage":"providers[{i}]: 'provider' must be a non-empty string","messagePattern":"providers\\[(.+?)\\]: 'provider' must be a non-empty string","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/authentication/config.py","lineNumber":147,"sourceCode":"        hosts = entry_raw.get(\"hosts\")\n        if not isinstance(hosts, list) or not hosts:\n            raise ValueError(f\"providers[{i}]: 'hosts' must be a non-empty array\")\n        if not all(isinstance(h, str) and h.strip() for h in hosts):\n            raise ValueError(f\"providers[{i}]: each host must be a non-empty string\")\n        # Normalize hosts: strip whitespace and lowercase\n        hosts = [h.strip().lower() for h in hosts]\n        # Reject dangerous wildcard forms (e.g. *github.com matches github.com.evil.com)\n        for h in hosts:\n            if not _is_valid_host_pattern(h):\n                raise ValueError(\n                    f\"providers[{i}]: invalid host pattern {h!r}. \"\n                    \"Only exact hostnames or '*.suffix' forms are allowed \"\n                    \"(e.g. 'github.com' or '*.visualstudio.com').\"\n                )\n\n        provider = entry_raw.get(\"provider\", \"\")\n        if not isinstance(provider, str) or not provider:\n            raise ValueError(f\"providers[{i}]: 'provider' must be a non-empty string\")\n\n        auth = entry_raw.get(\"auth\", \"\")\n        if not isinstance(auth, str) or not auth:\n            raise ValueError(f\"providers[{i}]: 'auth' must be a non-empty string\")\n\n        token = entry_raw.get(\"token\")\n        token_env = entry_raw.get(\"token_env\")\n\n        # Validate token/token_env types\n        if token is not None and (not isinstance(token, str) or not token.strip()):\n            raise ValueError(f\"providers[{i}]: 'token' must be a non-empty string\")\n        if token_env is not None and (not isinstance(token_env, str) or not token_env.strip()):\n            raise ValueError(f\"providers[{i}]: 'token_env' must be a non-empty string\")\n\n        # Validate provider+scheme compatibility\n        from . import get_provider as _get_provider\n        _prov = _get_provider(provider)\n        if _prov is None:","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/authentication/config.py#L129-L165","documentation":"Raised when the `provider` field of a `providers` entry is missing, not a string, or empty (after the `entry_raw.get(\"provider\", \"\")` default). The provider name must be a non-empty string because it is later resolved via `get_provider()` against `AUTH_REGISTRY`.","triggerScenarios":"A provider entry omits the `provider` key entirely, sets it to `\"\"`, or uses a non-string value (`null`, `123`, a list).","commonSituations":"Typos like `\"providers\"` instead of `\"provider\"`; YAML entries where the key was commented out or indented wrong so it parses as missing; template variable that rendered empty.","solutions":["Add a non-empty string `provider` key matching a registered provider (e.g. `github`)","Check YAML/JSON indentation so the key lands inside the intended provider entry","Verify no template expansion left `provider` empty"],"exampleFix":"# before\n- hosts: [\"github.com\"]\n  auth: bearer\n  token_env: GITHUB_TOKEN\n\n# after\n- hosts: [\"github.com\"]\n  provider: github\n  auth: bearer\n  token_env: GITHUB_TOKEN","handlingStrategy":"validation","validationCode":"def has_nonempty_string(entry: dict, key: str) -> bool:\n    v = entry.get(key)\n    return isinstance(v, str) and bool(v)\n\n# before load_auth_config:\nassert all(has_nonempty_string(e, \"provider\") for e in raw.get(\"providers\", []))","typeGuard":"def is_provider_entry(entry: object) -> bool:\n    return isinstance(entry, dict) and isinstance(entry.get(\"provider\"), str) and bool(entry[\"provider\"].strip())","tryCatchPattern":"try:\n    load_auth_config(raw)\nexcept ValueError as exc:\n    if \"'provider' must be a non-empty string\" in str(exc):\n        print(f\"providers[{extract_index(exc)}]: add the provider key\")\n        raise\n    raise","preventionTips":["Keep a validated example auth config as a template and copy from it","Validate provider entries with a JSON schema before passing them to the loader"],"tags":["authentication","config","validation"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}