{"record":{"id":"3351fe275cc5fed5","repo":"github/spec-kit","slug":"providers-i-auth-azure-ad-requires-tenant-i","errorCode":null,"errorMessage":"providers[{i}]: auth='azure-ad' requires 'tenant_id', 'client_id', and 'client_secret_env'","messagePattern":"providers\\[(.+?)\\]: auth='azure-ad' requires 'tenant_id', 'client_id', and 'client_secret_env'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/authentication/config.py","lineNumber":188,"sourceCode":"            )\n        if auth not in _prov.supported_auth_schemes:\n            raise ValueError(\n                f\"providers[{i}]: provider {provider!r} does not support \"\n                f\"auth scheme {auth!r}; supported: {list(_prov.supported_auth_schemes)}\"\n            )\n\n        # Validate token source based on auth scheme\n        if auth in (\"bearer\", \"basic-pat\"):\n            if not token and not token_env:\n                raise ValueError(\n                    f\"providers[{i}]: auth={auth!r} requires 'token' or 'token_env'\"\n                )\n        elif auth == \"azure-ad\":\n            tenant_id = entry_raw.get(\"tenant_id\")\n            client_id = entry_raw.get(\"client_id\")\n            client_secret_env = entry_raw.get(\"client_secret_env\")\n            if not all([tenant_id, client_id, client_secret_env]):\n                raise ValueError(\n                    f\"providers[{i}]: auth='azure-ad' requires \"\n                    \"'tenant_id', 'client_id', and 'client_secret_env'\"\n                )\n            for field_name, field_val in [\n                (\"tenant_id\", tenant_id),\n                (\"client_id\", client_id),\n                (\"client_secret_env\", client_secret_env),\n            ]:\n                if not isinstance(field_val, str) or not field_val.strip():\n                    raise ValueError(\n                        f\"providers[{i}]: '{field_name}' must be a non-empty string\"\n                    )\n        # azure-cli needs no extra fields\n\n        entries.append(\n            AuthConfigEntry(\n                hosts=tuple(hosts),\n                provider=provider,","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/authentication/config.py#L170-L206","documentation":"Raised for `auth: azure-ad` when any of `tenant_id`, `client_id`, or `client_secret_env` is missing/falsy in the provider entry. The Azure AD client-credentials flow needs all three, where the client secret is referenced indirectly through an environment variable name.","triggerScenarios":"An `auth: azure-ad` entry where one or more of the keys `tenant_id`, `client_id`, `client_secret_env` are absent, `null`, or empty strings — the `all([...])` check treats falsy values as missing.","commonSituations":"Migrating a bearer entry to azure-ad and only adding `tenant_id`; putting the secret value in `client_secret` instead of `client_secret_env` (the library deliberately never stores the raw secret); Azure DevOps PAT setups that actually want `basic-pat`.","solutions":["Set all three keys: `tenant_id`, `client_id`, `client_secret_env` (name of the env var holding the secret)","Export the secret in the environment under the name given to `client_secret_env`","If you only have a PAT, use `auth: basic-pat` with `token_env` instead of azure-ad"],"exampleFix":"# before\n- hosts: [\"*.visualstudio.com\"]\n  provider: azure-devops\n  auth: azure-ad\n  tenant_id: <tid>\n\n# after\n- hosts: [\"*.visualstudio.com\"]\n  provider: azure-devops\n  auth: azure-ad\n  tenant_id: <tid>\n  client_id: <cid>\n  client_secret_env: AZURE_CLIENT_SECRET","handlingStrategy":"validation","validationCode":"if entry.get(\"auth\") == \"azure-ad\":\n    missing = [k for k in (\"tenant_id\", \"client_id\", \"client_secret_env\") if not entry.get(k)]\n    if missing:\n        raise SystemExit(f\"azure-ad entry missing: {missing}\")","typeGuard":"def is_complete_azure_ad(entry: dict) -> bool:\n    if entry.get(\"auth\") != \"azure-ad\":\n        return True\n    return all(isinstance(entry.get(k), str) and entry[k].strip()\n               for k in (\"tenant_id\", \"client_id\", \"client_secret_env\"))","tryCatchPattern":"try:\n    load_auth_config(raw)\nexcept ValueError as exc:\n    if \"auth='azure-ad' requires\" in str(exc):\n        # collect the three fields (e.g. via az ad sp create-for-rbac) and retry\n        raise\n    raise","preventionTips":["azure-ad needs exactly tenant_id + client_id + client_secret_env; the raw secret never goes in the file, only its env var name","Generate service-principal credentials with `az ad sp create-for-rbac` and export the secret env var first"],"tags":["authentication","azure","config","validation"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}