{"record":{"id":"d07717b22f85f8d4","repo":"conductor-oss/conductor","slug":"oauth-token-request-failed-http-code","errorCode":null,"errorMessage":"OAuth token request failed: HTTP {code}","messagePattern":"OAuth token request failed: HTTP (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"ai/src/main/java/org/conductoross/conductor/ai/agent/credentials/OAuthTokenProvider.java","lineNumber":94,"sourceCode":"            refresh();\n        }\n        return cachedToken;\n    }\n\n    private void refresh() {\n        FormBody body =\n                new FormBody.Builder()\n                        .add(\"grant_type\", \"client_credentials\")\n                        .add(\"client_id\", clientId)\n                        .add(\"client_secret\", clientSecret)\n                        .add(\"scope\", scope)\n                        .build();\n\n        Request request = new Request.Builder().url(tokenEndpointUrl).post(body).build();\n\n        try (Response response = httpClient.newCall(request).execute()) {\n            if (!response.isSuccessful() || response.body() == null) {\n                throw new RuntimeException(\"OAuth token request failed: HTTP \" + response.code());\n            }\n            JsonNode json = MAPPER.readTree(response.body().string());\n            cachedToken = json.get(\"access_token\").asText();\n            long expiresIn = json.has(\"expires_in\") ? json.get(\"expires_in\").asLong() : 3600L;\n            expiresAt = Instant.now().plusSeconds(expiresIn);\n            log.debug(\"OAuth token refreshed, expires at {}\", expiresAt);\n        } catch (IOException e) {\n            throw new RuntimeException(\"Failed to acquire OAuth token from \" + tokenEndpointUrl, e);\n        }\n    }\n}\n","sourceCodeStart":76,"sourceCodeEnd":106,"githubUrl":"https://github.com/conductor-oss/conductor/blob/cf7c3e4a8adfb158be778ab1ec525323c363cd3a/ai/src/main/java/org/conductoross/conductor/ai/agent/credentials/OAuthTokenProvider.java#L76-L106","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before — wrong scope for Azure\ncredentials:\n  oauth:\n    scope: \"https://graph.microsoft.com\"\n// after\ncredentials:\n  oauth:\n    scope: \"https://graph.microsoft.com/.default\"","handlingStrategy":"try-catch","validationCode":"// Probe credentials independently before the workflow runs, e.g. a curl-equivalent health check\n// (kept here as a conceptual precondition; do the real check with your IdP SDK/CLI)","typeGuard":null,"tryCatchPattern":"try {\n    return tokenProvider.getToken();\n} catch (RuntimeException e) {\n    // message contains the HTTP code; 4xx == fix config, not a transient failure\n    if (e.getMessage() != null && e.getMessage().contains(\"HTTP 4\")) {\n        log.error(\"OAuth credentials rejected (config error, not retrying): {}\", e.getMessage());\n        throw e;\n    }\n    throw e;\n}","preventionTips":["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."],"tags":["oauth","auth","azure","network","credentials","runtimeexception"],"backgroundTag":null,"analyzedSha":"cf7c3e4a8adfb158be778ab1ec525323c363cd3a","analyzedAt":"2026-08-14T03:33:19.897Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}