crewAIInc/crewAI · error · ValueError

Authorization callback not set

Error message

Authorization callback not set

What it means

In the OAuth2 authorization-code flow of crewai's A2A client, _fetch_initial_token builds the consent URL and must hand it to the user via the registered callback to obtain an authorization code. If set_authorization_callback() was never called, the token exchange cannot even start and raises ValueError('Authorization callback not set').

Source

Thrown at lib/crewai/src/crewai/a2a/auth/client_schemes.py:500

        Args:
            client: HTTP client for making token request.

        Raises:
            ValueError: If authorization callback is not set.
            httpx.HTTPStatusError: If token request fails.
        """
        params = {
            "response_type": "code",
            "client_id": self.client_id,
            "redirect_uri": self.redirect_uri,
            "scope": " ".join(self.scopes),
        }
        auth_url = f"{self.authorization_url}?{urllib.parse.urlencode(params)}"

        if self._authorization_callback is None:
            msg = "Authorization callback not set"
            raise ValueError(msg)
        auth_code = await self._authorization_callback(auth_url)

        data = {
            "grant_type": "authorization_code",
            "code": auth_code,
            "client_id": self.client_id,
            "client_secret": self.client_secret,
            "redirect_uri": self.redirect_uri,
        }

        response = await client.post(self.token_url, data=data)
        response.raise_for_status()

        token_data = response.json()
        self._access_token = token_data["access_token"]
        self._refresh_token = token_data.get("refresh_token")

        expires_in = token_data.get("expires_in", 3600)

View on GitHub (pinned to 754d7323be)

Solutions

  1. Call scheme.set_authorization_callback(callback) before the first authenticated request.
  2. For headless flows, implement a callback that logs the URL and reads the code from a queue/file.
  3. If interactive consent is not possible, switch to a client-credentials or API-key scheme instead of authorization_code.

Example fix

# before
response = await client.fetch_token(...)  # _authorization_callback None -> ValueError

# after
async def get_code(url: str) -> str:
    print('Visit:', url)
    return input('Code: ')

scheme.set_authorization_callback(get_code)
response = await client.fetch_token(...)
Defensive patterns

Strategy: validation

Validate before calling

scheme = OAuth2ClientScheme(...)
if getattr(scheme, '_authorization_callback', None) is None:
    raise SystemExit('OAuth2 code flow needs set_authorization_callback() before first token fetch')

Type guard

def has_auth_callback(scheme) -> bool:
    return getattr(scheme, '_authorization_callback', None) is not None

Try / catch

try:
    await scheme._fetch_initial_token(client)
except ValueError as e:
    if 'Authorization callback not set' in str(e):
        scheme.set_authorization_callback(consent)
        await scheme._fetch_initial_token(client)
    else:
        raise

Prevention

When it happens

Trigger: Triggering the initial token fetch (no cached access token) on an OAuth2ClientScheme whose _authorization_callback is None - typically the first authentication attempt after object creation without setup.

Common situations: Automated/headless A2A clients where nobody wired a consent callback; refactors that construct the scheme in a different place than where the callback was registered; assuming client_credentials behavior from an authorization_code scheme.

Related errors


AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15). Data as JSON: /api/errors/f470fc5190a14582. Report an issue: GitHub.