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
- 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).
- Confirm the client secret in config matches the current app-registration secret and has not expired.
- Verify the scope value is exactly what the IdP expects (Azure wants 'api://<guid>/.default' or 'https://graph.microsoft.com/.default').
- 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
- Store client_id/secret/scope/tenantId in a secret manager and validate them at deploy time with a token request.
- For Azure, use the '.default' scope and confirm the app registration grants client_credentials.
- Alert on HTTP 4xx from the token endpoint — it almost always means a rotated/expired secret.
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
- Failed to acquire OAuth token from {tokenEndpointUrl}
- Error communicating with Azure
- A2A stream to {endpoint} failed: {message}
- A2A call '{method}' to {endpoint} failed: {message}
- token must not be blank
AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14).
Data as JSON: /api/errors/d07717b22f85f8d4.
Report an issue: GitHub.