{"record":{"id":"139e1ef758d76f51","repo":"BerriAI/litellm","slug":"github-copilot-api-key-is-required-please-authent-139e1e","errorCode":null,"errorMessage":"GitHub Copilot API key is required. Please authenticate via OAuth Device Flow.","messagePattern":"GitHub Copilot API key is required\\. Please authenticate via OAuth Device Flow\\.","errorType":"exception","errorClass":"AuthenticationError","httpStatus":401,"severity":"critical","filePath":"litellm/llms/github_copilot/responses/transformation.py","lineNumber":206,"sourceCode":"        \"\"\"\n        Validate environment and set up headers for GitHub Copilot API.\n\n        Uses the Authenticator to obtain GitHub Copilot API key via OAuth Device Flow,\n        then configures all required headers for the Responses API.\n\n        Headers include:\n        - Authorization with API key\n        - Standard GitHub Copilot headers (editor-version, user-agent, etc.)\n        - X-Initiator based on input analysis\n        - copilot-vision-request if vision content detected\n        - User-provided extra_headers (merged with priority)\n        \"\"\"\n        try:\n            # Get GitHub Copilot API key via OAuth\n            api_key: Final = self.authenticator.get_api_key()\n\n            if not api_key:\n                raise AuthenticationError(\n                    model=model,\n                    llm_provider=\"github_copilot\",\n                    message=\"GitHub Copilot API key is required. Please authenticate via OAuth Device Flow.\",\n                )\n\n            # Get default headers (from copilot-api configuration)\n            default_headers: Final = get_copilot_default_headers(api_key)\n\n            # Merge with existing headers (user's extra_headers take priority)\n            merged_headers: Final = {**default_headers, **headers}\n\n            # Analyze input to determine additional headers\n            input_param: Final = self._get_input_from_params(litellm_params)\n\n            # Add X-Initiator header based on input analysis\n            if input_param is not None:\n                initiator: Final = self._get_initiator(input_param)\n                merged_headers[\"X-Initiator\"] = initiator","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/github_copilot/responses/transformation.py#L188-L224","documentation":"Raised in the responses transformation's validate_environment when get_api_key() returns a falsy value (None/'') without raising. Defensive branch mirroring error 1631 for the /responses path: a real auth failure raises GetAPIKeyError (caught separately), so this fires only when an empty key came back — typically a corrupted or hand-built api-key.json with an empty 'token'.","triggerScenarios":"Calling litellm.responses(..., model=\"github_copilot/...\") when the token cache holds an empty-string token; e.g. templated cache files deployed with placeholder values, or a truncated cache write from a killed process leaving token empty.","commonSituations":"Baking token caches into container images with placeholder JSON; secrets managers syncing an empty secret into the cache; partial cache corruption after OOM kills.","solutions":["Delete the api-key.json (and access-token) cache files and re-run the device-flow login.","Never hand-author the cache — always let the authenticator write it after a real OAuth flow.","Validate any pre-seeded cache contains a non-empty token before deploying (jq '.token | length > 0').","Retry the responses call once the cache is repopulated by a warmup request."],"exampleFix":"# before: image ships placeholder cache {\"token\": \"\"}\nlitellm.responses(model=\"github_copilot/gpt-4o\", input=\"hello\")\n\n# after: no placeholder caches; authenticate properly\nimport pathlib\ncache = pathlib.Path(\"~/.litellm/github_copilot/api-key.json\").expanduser()\nif cache.exists() and not (__import__('json').loads(cache.read_text()).get('token')):\n    cache.unlink()  # drop empty-token cache so the authenticator re-authenticates\n# litellm --login github_copilot  (one-time, completes device flow)\nlitellm.responses(model=\"github_copilot/gpt-4o\", input=\"hello\")","handlingStrategy":"validation","validationCode":"import json, pathlib\n\ncache = pathlib.Path(\"~/.litellm/github_copilot/api-key.json\").expanduser()\nif cache.exists():\n    try:\n        ok = bool(json.loads(cache.read_text()).get(\"token\"))\n    except json.JSONDecodeError:\n        ok = False\n    if not ok:\n        cache.unlink()\n        print(\"evicted invalid Copilot cache; next call re-authenticates\")","typeGuard":"def copilot_responses_cache_valid(cache_path: pathlib.Path) -> bool:\n    \"\"\"Cache must exist and carry a non-empty token string.\"\"\"\n    try:\n        data = json.loads(cache_path.read_text())\n    except (FileNotFoundError, json.JSONDecodeError, OSError):\n        return False\n    return isinstance(data.get(\"token\"), str) and len(data[\"token\"]) > 0","tryCatchPattern":"from litellm.exceptions import AuthenticationError\n\ntry:\n    resp = litellm.responses(model=\"github_copilot/gpt-4o\", input=\"hi\")\nexcept AuthenticationError as e:\n    if \"OAuth Device Flow\" in str(e):\n        clear_copilot_cache()\n        raise SystemExit(\"Run 'litellm --login github_copilot', then restart\") from e\n    raise","preventionTips":["Reject placeholder/empty token caches in image builds (jq guard in CI).","Only the authenticator writes cache files.","Re-run device flow after any manual cache surgery.","Add cache validation to deployment smoke tests."],"tags":["github-copilot","responses-api","authentication","oauth","empty-token"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}