{"record":{"id":"5ab94ff37b241ae5","repo":"oven-sh/bun","slug":"azure-auth-failed-response-status-await-re","errorCode":null,"errorMessage":"[azure] Auth failed: ${response.status} ${await response.text()}","messagePattern":"\\[azure\\] Auth failed: (.+?) (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"scripts/azure.mjs","lineNumber":72,"sourceCode":"async function getAccessToken() {\n  if (_accessToken && Date.now() < _tokenExpiry - 300_000) {\n    return _accessToken;\n  }\n\n  const { tenantId, clientId, clientSecret } = config();\n  const response = await fetch(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n    body: new URLSearchParams({\n      grant_type: \"client_credentials\",\n      client_id: clientId,\n      client_secret: clientSecret,\n      scope: \"https://management.azure.com/.default\",\n    }),\n  });\n\n  if (!response.ok) {\n    throw new Error(`[azure] Auth failed: ${response.status} ${await response.text()}`);\n  }\n\n  const data = await response.json();\n  _accessToken = data.access_token;\n  _tokenExpiry = Date.now() + data.expires_in * 1000;\n  return _accessToken;\n}\n\n// ============================================================================\n// REST Client\n// ============================================================================\n\n/**\n * @param {\"GET\"|\"PUT\"|\"POST\"|\"PATCH\"|\"DELETE\"} method\n * @param {string} path - Relative path under management.azure.com, or absolute URL\n * @param {object} [body]\n * @param {string} [apiVersion]\n */","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/oven-sh/bun/blob/8c5296ac459e8252d3cd702f3fbcbb0c249d95d5/scripts/azure.mjs#L54-L90","documentation":"OAuth2 client-credentials token request to login.microsoftonline.com returned non-2xx; the status and body are included in the message. This is the entry point for every Azure REST call in the module (the token is cached until _tokenExpiry), so nothing Azure-related works until it is fixed.","triggerScenarios":"Wrong AZURE_TENANT_ID or AZURE_CLIENT_ID; expired/rotated AZURE_CLIENT_SECRET (AADSTS7000215 / invalid_client); secret with trailing whitespace or not URL-encoded properly in the form body; the service principal was deleted.","commonSituations":"Client secret expired (Azure secrets max out at 2 years) and CI still holds the old value; values copied with quotes or spaces; tenant id swapped for subscription id.","solutions":["Read the response body in the error — AADSTS codes state exactly what is wrong","Verify the triple: az login --service-principal -u clientId -p clientSecret --tenant tenantId","Rotate the client secret in Azure and update the stored CI secret if it expired","Confirm tenantId is the directory (tenant) GUID, not the subscription GUID"],"exampleFix":"# before\n# stale secret in the store -> 401 invalid_client\n\n# after\n$ az ad sp credential reset --id $AZURE_CLIENT_ID  # rotate\n# store the new secret, then verify:\n$ az login --service-principal -u $AZURE_CLIENT_ID -p $AZURE_CLIENT_SECRET --tenant $AZURE_TENANT_ID","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await getAccessToken();\n} catch (err) {\n  if (/Auth failed/.test(err.message)) {\n    console.error('Azure credentials rejected (check tenant/client id/secret expiry):', err.message);\n    process.exit(1); // config problem: do not retry\n  }\n  throw err;\n}","preventionTips":["Rotate AZURE_CLIENT_SECRET on a schedule before the 2-year expiry","Validate the credential triple with az login --service-principal before CI runs","Parse AADSTS codes from the response body for precise failures"],"tags":["azure","authentication","oauth","secrets"],"backgroundTag":null,"analyzedSha":"8c5296ac459e8252d3cd702f3fbcbb0c249d95d5","analyzedAt":"2026-08-16T08:01:58.794Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}