oven-sh/bun · error

Azure auth failed: ${response.status}

Error message

Azure auth failed: ${response.status}

What it means

getAzureToken() POSTs an OAuth2 client-credentials grant to https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token; any non-2xx throws with just the HTTP status. Common statuses: 400 invalid_client (bad/expired secret, wrong client id), 401 (wrong tenant), 403 (conditional access blocking).

Source

Thrown at scripts/machine.mjs:1140

 * @property {number} [memoryGb]
 * @property {number} [diskSizeGb]
 * @property {boolean} [preemptible]
 * @property {boolean} [detached]
 * @property {Record<string, unknown>} [tags]
 * @property {boolean} [bootstrap]
 * @property {boolean} [ci]
 * @property {boolean} [rdp]
 * @property {string} [userData]
 * @property {SshKey[]} sshKeys
 */

async function getAzureToken(tenantId, clientId, clientSecret) {
  const response = await fetch(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: `grant_type=client_credentials&client_id=${clientId}&client_secret=${encodeURIComponent(clientSecret)}&scope=https://management.azure.com/.default`,
  });
  if (!response.ok) throw new Error(`Azure auth failed: ${response.status}`);
  const data = await response.json();
  return data.access_token;
}

/**
 * Build a Windows image using Packer (Azure only).
 * Packer handles VM creation, bootstrap, sysprep, and gallery capture via WinRM.
 * This eliminates all the Azure Run Command issues (output truncation, x64 emulation,
 * PATH not refreshing, stderr false positives, quote escaping).
 */
async function buildWindowsImageWithPacker({ os, arch, release, command, ci, agentPath, bootstrapPath }) {
  const { getSecret } = await import("./utils.mjs");

  // Determine Packer template
  const templateName = arch === "aarch64" ? "windows-arm64" : "windows-x64";
  const templateDir = resolve(import.meta.dirname, "packer");
  const templateFile = join(templateDir, `${templateName}.pkr.hcl`);

View on GitHub (pinned to 8c5296ac45)

Solutions

  1. Verify all four AZURE_* secrets resolve non-empty before the call
  2. Test the principal directly: `az login --service-principal -u <clientId> -p <secret> --tenant <tenantId>`
  3. Rotate the client secret and update the Buildkite secret if it expired
  4. Map the status: 400 invalid_client = bad secret/id, 401 = wrong tenant, 403 = conditional access

Example fix

// before
const token = await getAzureToken(tenantId, clientId, clientSecret);

// after
for (const [k, v] of Object.entries({ tenantId, clientId, clientSecret })) {
  if (!v) throw new Error(`missing Azure credential: ${k}`);
}
const token = await getAzureToken(tenantId, clientId, clientSecret);
Defensive patterns

Strategy: validation

Validate before calling

for (const [k, v] of Object.entries({ AZURE_TENANT_ID: tenantId, AZURE_CLIENT_ID: clientId, AZURE_CLIENT_SECRET: clientSecret })) {
  if (!v) throw new Error(`missing Azure credential: ${k}`);
}

Try / catch

try {
  token = await getAzureToken(tenantId, clientId, clientSecret);
} catch (error) {
  if (/400/.test(String(error))) throw new Error('invalid/expired AZURE_CLIENT_SECRET or wrong client id');
  if (/401/.test(String(error))) throw new Error('wrong AZURE_TENANT_ID');
  throw error;
}

Prevention

When it happens

Trigger: AZURE_CLIENT_SECRET expired or rotated and the Buildkite secret is stale; wrong AZURE_TENANT_ID or AZURE_CLIENT_ID; one of the secret values is undefined so the URL/body is malformed; network egress blocked to login.microsoftonline.com.

Common situations: Service principal secret past its expiry in CI; getSecret() returning empty because the Buildkite secret wasn't created; tenant GUID copy-paste error.

Related errors


AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16). Data as JSON: /api/errors/8a4dfb55aa9ca617. Report an issue: GitHub.