HKUDS/DeepTutor · error · RuntimeError

GitHub Copilot auth is unavailable. Validate local Copilot…

Error message

GitHub Copilot auth is unavailable. Validate local Copilot auth or log in first.

What it means

The GitHub Copilot provider exchanges a locally stored GitHub OAuth token for a short-lived Copilot token; if no stored GitHub token exists, _exchange_token raises RuntimeError telling the user to log in first.

Solutions

  1. Run `deeptutor provider login github-copilot` (or sign in via GitHub Copilot tooling) to populate the token store.
  2. Verify the token store file exists and contains an oauth token for github.com.
  3. Re-run after checking file permissions on the config directory.
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def copilot_auth_present() -> bool:
    # token store location used by the provider
    return Path.home().joinpath('.config/github-copilot/hosts.json').exists()

Try / catch

try:
    resp = await provider.chat(messages)
except RuntimeError as e:
    if 'log in first' in str(e):
        prompt_user_to_login('deeptutor provider login github-copilot')
    else:
        raise

Prevention

When it happens

Trigger: Calling chat via the Copilot provider with an empty/expired GitHub token store (e.g. ~/.config/github-copilot/ missing or devoid of hosts entries); _chat_impl → _exchange_token finds _load_stored_github_token() returning None.

Common situations: Never having run `deeptutor provider login github-copilot`; token store deleted; Copilot plugin auth files not present on a new machine/CI.

Related errors


AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27). Data as JSON: /api/errors/d0238a51ccdc73ad. Report an issue: GitHub.

Appendix: source

Thrown at deeptutor/services/llm/provider_core/github_copilot_provider.py:65

    async def _load_stored_github_token(self) -> str | None:
        try:
            from oauth_cli_kit.storage import FileTokenStorage
        except ImportError:
            return None
        storage = FileTokenStorage(  # nosec B106 - token_filename is a file name, not a password.
            token_filename="github-copilot.json",
            app_name="nanobot",
            import_codex_cli=False,
        )
        token = storage.load()
        if not token or not getattr(token, "access", None):
            return None
        return str(token.access)

    async def _exchange_token(self) -> str:
        github_token = await self._load_stored_github_token()
        if not github_token:
            raise RuntimeError(
                "GitHub Copilot auth is unavailable. Validate local Copilot auth or log in first."
            )

        timeout = httpx.Timeout(20.0, connect=20.0)
        async with httpx.AsyncClient(
            timeout=timeout, follow_redirects=True, trust_env=True
        ) as client:
            response = await client.get(
                DEFAULT_COPILOT_TOKEN_URL,
                headers={
                    "Authorization": f"token {github_token}",
                    "Accept": "application/json",
                    "User-Agent": USER_AGENT,
                    "Editor-Version": EDITOR_VERSION,
                    "Editor-Plugin-Version": EDITOR_PLUGIN_VERSION,
                },
            )
            response.raise_for_status()

View on GitHub (pinned to 3e82f13042)