conductor-oss/conductor · error · RuntimeException

OAuth token request failed: HTTP {code}

Error message

OAuth token request failed: HTTP {code}

What it means

Thrown by OAuthTokenProvider.refresh when the OAuth 2.0 token endpoint returns a non-2xx status or an empty body. The provider uses the client_credentials grant (client_id, client_secret, scope) against tokenEndpointUrl; any rejection by the identity provider surfaces as this RuntimeException with the failing HTTP code. The message tells you the IdP refused the request but not why — the response body (which may contain an error/error_description) is discarded.

Source

Thrown at ai/src/main/java/org/conductoross/conductor/ai/agent/credentials/OAuthTokenProvider.java:94

            refresh();
        }
        return cachedToken;
    }

    private void refresh() {
        FormBody body =
                new FormBody.Builder()
                        .add("grant_type", "client_credentials")
                        .add("client_id", clientId)
                        .add("client_secret", clientSecret)
                        .add("scope", scope)
                        .build();

        Request request = new Request.Builder().url(tokenEndpointUrl).post(body).build();

        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful() || response.body() == null) {
                throw new RuntimeException("OAuth token request failed: HTTP " + response.code());
            }
            JsonNode json = MAPPER.readTree(response.body().string());
            cachedToken = json.get("access_token").asText();
            long expiresIn = json.has("expires_in") ? json.get("expires_in").asLong() : 3600L;
            expiresAt = Instant.now().plusSeconds(expiresIn);
            log.debug("OAuth token refreshed, expires at {}", expiresAt);
        } catch (IOException e) {
            throw new RuntimeException("Failed to acquire OAuth token from " + tokenEndpointUrl, e);
        }
    }
}

View on GitHub (pinned to cf7c3e4a8a)

Solutions

  1. Reproduce the grant outside Conductor with curl against the same tokenEndpointUrl using the same client_id/secret/scope to see the IdP's error body (invalid_client vs invalid_scope narrows it fast).
  2. Confirm the client secret in config matches the current app-registration secret and has not expired.
  3. Verify the scope value is exactly what the IdP expects (Azure wants 'api://<guid>/.default' or 'https://graph.microsoft.com/.default').
  4. Check the tokenEndpointUrl is correct for the tenant (e.g. https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token).

Example fix

// before — wrong scope for Azure
credentials:
  oauth:
    scope: "https://graph.microsoft.com"
// after
credentials:
  oauth:
    scope: "https://graph.microsoft.com/.default"
Defensive patterns

Strategy: try-catch

Validate before calling

// Probe credentials independently before the workflow runs, e.g. a curl-equivalent health check
// (kept here as a conceptual precondition; do the real check with your IdP SDK/CLI)

Try / catch

try {
    return tokenProvider.getToken();
} catch (RuntimeException e) {
    // message contains the HTTP code; 4xx == fix config, not a transient failure
    if (e.getMessage() != null && e.getMessage().contains("HTTP 4")) {
        log.error("OAuth credentials rejected (config error, not retrying): {}", e.getMessage());
        throw e;
    }
    throw e;
}

Prevention

When it happens

Trigger: The token endpoint URL, client_id, client_secret, or scope is wrong/expired. Concretely: HTTP 400 invalid_client (bad secret), 400 invalid_scope (scope not granted to the app), 401 (wrong client_id/secret), 404 (wrong tenantId/endpoint), or the endpoint requires a different grant type.

Common situations: Rotated an Azure Entra ID / Okta / Auth0 client secret but did not update Conductor config; used the wrong scope URI (e.g. missing the /.default suffix for Azure); pointed at a tenant that has the app registration disabled; clock skew causing assertion rejection.

Related errors


AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14). Data as JSON: /api/errors/d07717b22f85f8d4. Report an issue: GitHub.