{"record":{"id":"b699a5a4b3b633e7","repo":"headroomlabs-ai/headroom","slug":"copilot-oauth-token-must-not-be-empty","errorCode":null,"errorMessage":"Copilot OAuth token must not be empty.","messagePattern":"Copilot OAuth token must not be empty\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"headroom/copilot_auth.py","lineNumber":499,"sourceCode":"        logger.debug(\"Unable to read Headroom Copilot auth file: %s\", exc)\n        return None\n\n    if not isinstance(payload, dict) or payload.get(\"type\") != \"oauth\":\n        return None\n    token = payload.get(\"refresh\")\n    return token.strip() if isinstance(token, str) and token.strip() else None\n\n\ndef save_headroom_copilot_oauth_token(\n    token: str,\n    *,\n    domain: str = DEFAULT_GITHUB_HOST,\n) -> Path:\n    \"\"\"Persist the Copilot OAuth token returned by GitHub device login.\"\"\"\n\n    token = token.strip()\n    if not token:\n        raise ValueError(\"Copilot OAuth token must not be empty.\")\n\n    path = headroom_copilot_auth_path()\n    path.parent.mkdir(parents=True, exist_ok=True)\n    body: dict[str, Any] = {\n        \"type\": \"oauth\",\n        \"provider\": \"github-copilot\",\n        \"refresh\": token,\n        \"domain\": _github_oauth_domain(domain),\n        \"created_at\": int(time.time()),\n    }\n    path.write_text(json.dumps(body, indent=2, sort_keys=True) + \"\\n\", encoding=\"utf-8\")\n    try:\n        path.chmod(0o600)\n    except OSError:\n        pass\n    return path\n\n","sourceCodeStart":481,"sourceCodeEnd":517,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/copilot_auth.py#L481-L517","documentation":"save_headroom_copilot_oauth_token() strips the incoming token and raises ValueError if nothing remains. This is the persistence step for the token returned by GitHub's device login flow, and the guard prevents writing an auth file whose 'refresh' field is an empty string — which would later be silently unusable. The file (under headroom_copilot_auth_path()) is only written after the check plus a parent mkdir.","triggerScenarios":"Calling save_headroom_copilot_oauth_token(token) with an empty string, a whitespace-only string, or None coerced to str. In practice this happens when a caller passes an unvalidated value extracted from an OAuth payload where the token field was absent.","commonSituations":"Wiring a custom GitHub device-flow client and forwarding payload.get('access_token') (None) directly; copy-paste scripts that read a token from an env var that was never set (os.environ.get gives None or ''); upstream API change renaming the token field so extraction yields ''.","solutions":["Validate before saving: check `token and token.strip()` and surface a meaningful auth error to the user","Trace where the token came from — usually poll_copilot_device_authorization returned successfully but the caller stored the wrong field","Never persist empty credentials; re-run the device authorization flow to obtain a real token"],"exampleFix":"# before\ntoken = payload.get(\"access_token\") or \"\"\nsave_headroom_copilot_oauth_token(token)  # ValueError\n\n# after\ntoken = (payload.get(\"access_token\") or \"\").strip()\nif not token:\n    raise RuntimeError(\"Device flow returned no access token\")\nsave_headroom_copilot_oauth_token(token)","handlingStrategy":"validation","validationCode":"token = (token or \"\").strip()\nif not token:\n    raise ValueError(\"Refusing to save empty Copilot OAuth token — re-run device login\")","typeGuard":"def is_valid_oauth_token(value: object) -> bool:\n    return isinstance(value, str) and len(value.strip()) > 0","tryCatchPattern":"try:\n    save_headroom_copilot_oauth_token(token)\nexcept ValueError:\n    # empty credential: never retry with the same value; restart the device flow\n    raise RuntimeError(\"Device flow produced no token; restart authorization\")","preventionTips":["Validate token presence at the extraction site, not at persistence","Treat empty OAuth fields as an upstream contract change — log payload keys (never values)","Never write auth files with empty/blank credential fields"],"tags":["validation","auth","copilot","oauth","token"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}