diff --git a/scripts/preflight-claude.ps1 b/scripts/preflight-claude.ps1 index 11e9071..16cfd18 100644 --- a/scripts/preflight-claude.ps1 +++ b/scripts/preflight-claude.ps1 @@ -32,15 +32,19 @@ 0 Preflight passed (or skipped — see warnings). 4 Marketplace offer not found (typo in a model name, or model not in the Anthropic-on-Foundry catalog yet). - 6 Insufficient quota (used + requested > limit). + 6 Insufficient quota (Terraform variant only; on Bicep this is a + warning because azd's own ARM preflight already surfaces it). The preflight is best-effort. If `CLAUDE_ORGANIZATION_NAME` / `AZURE_LOCATION` aren't set, or `az` isn't installed / logged in, it warns and exits 0 so `azd up` can continue (azd / Bicep will prompt for any missing parameter; the RP surfaces catalog / quota errors at - deploy time, just less ergonomically). The marketplace-offer and - quota checks remain hard fails when they CAN run, because they - catch the most common cause of opaque deploy failures. + deploy time, just less ergonomically). The marketplace-offer check + remains a hard fail in both variants when it can run. The quota check + is a hard fail only on the Terraform variant because `azapi_resource` + swallows quota into the opaque `715-123420`; on Bicep it's a warning + because azd's own provisionParametersResolver prints `InsufficientQuota` + next and prompts to continue. #> [CmdletBinding()] @@ -59,6 +63,23 @@ function Warn([string]$message) { Write-Host "Preflight: $message" -ForegroundColor Yellow } +# Detect which IaC variant the preprovision hook is running under. The hook +# always fires from inside the variant folder (`infra-bicep/` or +# `infra-terraform/`), so the local `azure.yaml` tells us which provider azd +# is about to drive. This decides whether the quota check is a hard fail +# (Terraform: `azapi_resource` bypasses ARM preflight and the RP returns the +# opaque `400 715-123420`, so we MUST catch it here) or a warning (Bicep: +# azd's own `provisionParametersResolver` already runs ARM preflight and +# prints a clean `InsufficientQuota` message + prompts to continue, so a +# hard fail here just blocks that better UX). +$variant = 'unknown' +$azureYaml = Join-Path (Get-Location) 'azure.yaml' +if (Test-Path $azureYaml) { + $yaml = Get-Content $azureYaml -Raw + if ($yaml -match '(?m)^\s*provider:\s*bicep') { $variant = 'bicep' } + elseif ($yaml -match '(?m)^\s*provider:\s*terraform') { $variant = 'terraform' } +} + # --- 1. Required env vars --------------------------------------------------- if (-not $env:CLAUDE_ORGANIZATION_NAME) { Warn "CLAUDE_ORGANIZATION_NAME is not set. azd will prompt for the 'claudeOrganizationName' Bicep parameter at provision time. To skip the prompt: azd env set CLAUDE_ORGANIZATION_NAME 'Your Org'" @@ -164,7 +185,7 @@ $msg $available = $limit - $current if ($available -lt $capacity) { $upperFamily = $family.ToUpper() - Fail 6 @" + $quotaMsg = @" Insufficient quota for '$modelName' (family=$family) in '$location'. Requested capacity: $capacity TPM (thousands) @@ -185,6 +206,17 @@ opaque '400 715-123420' error because azapi bypasses ARM preflight validation. Bicep and 'az deployment group create' show the real 'InsufficientQuota' message because they go through ARM preflight. "@ + if ($variant -eq 'bicep') { + # Bicep variant: azd's own ARM preflight will print the same + # InsufficientQuota next and prompt to continue. Warn so the + # diagnostic is up front, then let azd take over. + Write-Host "" + Write-Host "Preflight WARNING: $quotaMsg" -ForegroundColor Yellow + Write-Host "(Continuing — azd's Bicep ARM preflight will repeat this and prompt to continue.)" -ForegroundColor Yellow + Write-Host "" + continue + } + Fail 6 $quotaMsg } Write-Host "Preflight: '$modelName' quota OK ($capacity requested, $available available of $limit in $location)." -ForegroundColor Green } else { diff --git a/scripts/preflight-claude.sh b/scripts/preflight-claude.sh index d2b14a4..d74c3ac 100644 --- a/scripts/preflight-claude.sh +++ b/scripts/preflight-claude.sh @@ -21,15 +21,19 @@ # Exit codes: # 0 Preflight passed (or skipped — see warnings). # 4 Marketplace offer not found. -# 6 Insufficient quota. +# 6 Insufficient quota (Terraform variant only; on Bicep this is a +# warning because azd's own ARM preflight already surfaces it). # # The preflight is best-effort. If CLAUDE_ORGANIZATION_NAME / AZURE_LOCATION # aren't set, or `az` isn't installed / logged in, it warns and exits 0 so # `azd up` can continue (azd / Bicep will prompt for any missing parameter; # the RP surfaces catalog / quota errors at deploy time, just less -# ergonomically). The marketplace-offer and quota checks remain hard fails -# when they CAN run, because they catch the most common cause of opaque -# deploy failures. +# ergonomically). The marketplace-offer check remains a hard fail in both +# variants when it can run. The quota check is a hard fail only on the +# Terraform variant because azapi_resource swallows quota into the opaque +# 715-123420; on Bicep it's a warning because azd's own +# provisionParametersResolver prints InsufficientQuota next and prompts to +# continue. set -euo pipefail @@ -43,6 +47,24 @@ warn() { printf 'Preflight: %s\n' "$*" >&2 } +# Detect which IaC variant the preprovision hook is running under. The hook +# always fires from inside the variant folder (infra-bicep/ or +# infra-terraform/), so the local azure.yaml tells us which provider azd is +# about to drive. This decides whether the quota check is a hard fail +# (Terraform: azapi_resource bypasses ARM preflight and the RP returns the +# opaque 400 715-123420, so we MUST catch it here) or a warning (Bicep: +# azd's own provisionParametersResolver already runs ARM preflight and prints +# a clean InsufficientQuota message + prompts to continue, so a hard fail +# here just blocks that better UX). +VARIANT="unknown" +if [ -f "./azure.yaml" ]; then + if grep -qE '^[[:space:]]*provider:[[:space:]]*bicep' ./azure.yaml; then + VARIANT="bicep" + elif grep -qE '^[[:space:]]*provider:[[:space:]]*terraform' ./azure.yaml; then + VARIANT="terraform" + fi +fi + # --- 1. Required env vars --------------------------------------------------- if [ -z "${CLAUDE_ORGANIZATION_NAME:-}" ]; then warn "CLAUDE_ORGANIZATION_NAME is not set. azd will prompt for the 'claudeOrganizationName' Bicep parameter at provision time. To skip the prompt: azd env set CLAUDE_ORGANIZATION_NAME 'Your Org'" @@ -153,7 +175,7 @@ $MP_RAW" AVAILABLE=$(( LIMIT_INT - CURRENT_INT )) if [ "$AVAILABLE" -lt "$CAPACITY" ]; then FAMILY_UPPER="$(echo "$FAMILY" | tr '[:lower:]' '[:upper:]')" - fail 6 "Insufficient quota for '$MODEL_NAME' (family=$FAMILY) in '$LOCATION'. + QUOTA_MSG="Insufficient quota for '$MODEL_NAME' (family=$FAMILY) in '$LOCATION'. Requested capacity: $CAPACITY TPM (thousands) Available: $AVAILABLE TPM (limit $LIMIT_INT, currently used $CURRENT_INT) @@ -172,6 +194,14 @@ Note: without this preflight, Terraform (azapi_resource) would fail with an opaque '400 715-123420' error because azapi bypasses ARM preflight validation. Bicep / 'az deployment group create' show the real 'InsufficientQuota' message because they go through ARM preflight." + if [ "$VARIANT" = "bicep" ]; then + # Bicep variant: azd's own ARM preflight will print the same + # InsufficientQuota next and prompt to continue. Warn so the + # diagnostic is up front, then let azd take over. + printf '\nPreflight WARNING: %s\n(Continuing — azd'"'"'s Bicep ARM preflight will repeat this and prompt to continue.)\n\n' "$QUOTA_MSG" >&2 + continue + fi + fail 6 "$QUOTA_MSG" fi echo "Preflight: '$MODEL_NAME' quota OK ($CAPACITY requested, $AVAILABLE available of $LIMIT_INT in $LOCATION)." else diff --git a/skills/claude-on-foundry/SKILL.md b/skills/claude-on-foundry/SKILL.md index e47b7d6..a5b09ca 100644 --- a/skills/claude-on-foundry/SKILL.md +++ b/skills/claude-on-foundry/SKILL.md @@ -92,7 +92,7 @@ azd env set CLAUDE_CODE_AUTO_INSTALL true azd up ``` -The `preprovision` hook runs `scripts/preflight-claude.ps1` automatically. It is **best-effort**: it warns and continues when `CLAUDE_ORGANIZATION_NAME` / `AZURE_LOCATION` aren't set (azd / Bicep will prompt) or when `az` isn't installed / logged in (the RP will surface any errors at deploy time). It still **hard-fails** on (a) a missing offer in the Anthropic catalog or (b) insufficient TPM quota when those checks can actually run — those are the cases that turn into opaque deploy failures otherwise. Never suggest bypassing it. +The `preprovision` hook runs `scripts/preflight-claude.ps1` automatically. It is **best-effort**: it warns and continues when `CLAUDE_ORGANIZATION_NAME` / `AZURE_LOCATION` aren't set (azd / Bicep will prompt) or when `az` isn't installed / logged in (the RP will surface any errors at deploy time). It still **hard-fails** on a missing offer in the Anthropic catalog (exit 4) when that check can run — that's the case that turns into an opaque deploy failure otherwise. The quota check is a **hard fail on Terraform** (because `azapi_resource` swallows quota into the opaque `715-123420`) and a **warning on Bicep** (because azd's own ARM preflight already prints `InsufficientQuota` and prompts to continue). Never suggest bypassing the preflight. The `postprovision` hook runs `scripts/configure-claude-code.ps1` to wire up Claude Code. The customer can re-run it any time without re-deploying: ```powershell