MANDATORY parallel execution patterns (30x speedup), monitor metrics aggregation, --output tsv formatting, and common pitfalls
Scanned 9/8/2026
Install to Claude Code
npx -y skills add cloudthinker-ai/CloudSkills --skill azure --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Azure?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/cloudthinker-ai-azure)More formats (shields.io, HTML) on the badges page.
---
name: azure
description: "MANDATORY parallel execution patterns (30x speedup), monitor metrics aggregation, --output tsv formatting, and common pitfalls"
connection_type: azure
preload: false
---
# Azure CLI Skill
Execute Azure CLI commands with proper credential injection.
## CLI Tips
### Parallel Execution Requirement (CRITICAL)
**ALL independent operations MUST run in parallel using background jobs (&) and wait**
ENFORCEMENT RULES:
- **FORBIDDEN**: Sequential loops like `for item in $items; do cmd $item; done` (causes O(n) runtime)
- **MANDATORY**: Every independent operation spawns a background job: `{ cmd1 } & { cmd2 } & { cmd3 } & wait`
- **DETECTION**: If your script processes N resources/metrics/regions and N > 1, the script MUST contain at least N background jobs
- **TIME IMPACT**: Sequential execution with 30 VMs × 2 seconds per call = 60 seconds. Parallel = 2 seconds (30x faster)
- **VALIDATION CHECKLIST** (agent must mentally verify before output):
- Count independent operations: \_\_\_
- Count background jobs (&): \_\_\_
- These numbers MUST match, or script will be REJECTED
- Do all operations depend on each other? (Only valid exception to parallel requirement)
PARALLEL PATTERN (CORRECT):
```bash
for vm in $vms; do
operation "$vm" & # ← Spawn as background job
done
wait # ← Wait for all to complete
```
SEQUENTIAL PATTERN (FORBIDDEN - ONLY if operations have data dependencies):
```bash
result=$(operation1)
operation2 "$result" # ← Only valid if operation2 requires operation1's output
```
### Agent Output Rules
- The script output is for the agent itself to read and process, NOT for human reading
- Do NOT add visual formatting, icons, or decorative elements (no emojis, borders, or separators)
- NEVER USE echo statements for section breaks, headers, or formatting (no "--------", "====", or similar)
- Focus on raw data extraction and minimal, parseable output
- Use plain text format with consistent delimiters for easy parsing
- Prioritize machine-readability over human presentation
- NEVER run commands or scripts that print, log, or expose environment variables, credentials, or Azure keys (e.g., AZURE_USERNAME, AZURE_PASSWORD, AZURE_TENANT)
### Execution Guidelines
- **PARALLEL EXECUTION IS MANDATORY**: Always use background jobs (`&`) and `wait` for independent operations
- Process multiple VMs/resources in parallel: `{ ... } &` with `wait` at the end
- Fetch multiple metrics for the same resource in parallel, then `wait` before processing
- Sequential loops are FORBIDDEN unless operations have strict dependencies
- Parallel execution reduces runtime from O(n \* time_per_operation) to O(max_operation_time) - use it always
- Always consolidate related steps into single CLI Bash script if possible
- Only use read-only commands (e.g., list, show, get) - never modify resources
- Always format CLI output as plain text (never JSON or table) so that it's easy for the agent to parse. For Azure use --output tsv for text output. Also use filtering/query flags to limit output to what is needed. These practices are crucial for efficiency and accuracy.
### Efficient CLI Script Example
**ANTI-PATTERN EXAMPLE (SEQUENTIAL - SLOW - UNACCEPTABLE)**
```bash
#!/bin/bash
# RUNTIME: ~60 seconds for 30 VMs (2 sec per call × 30)
END_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
START_TIME=$(date -u -d "30 days ago" +"%Y-%m-%dT%H:%M:%SZ")
echo "Azure VM Metrics Summary ($START_TIME to $END_TIME)"
# Get resource groups
az group list --output tsv --query "[*].[name]" | while read rg_name; do
vms=$(az vm list -g "$rg_name" --output tsv --query "[*].[name]")
if [ -n "$vms" ]; then
echo "Resource Group: $rg_name"
# This SEQUENTIAL loop is FORBIDDEN
echo "$vms" | while read vm_name; do
echo " VM: $vm_name"
# Sequential metric fetches - UNACCEPTABLE
az monitor metrics list \
--resource "/subscriptions/$(az account show --query id -o tsv)/resourceGroups/$rg_name/providers/Microsoft.Compute/virtualMachines/$vm_name" \
--metric "Percentage CPU" \
--start-time "$START_TIME" \
--end-time "$END_TIME" \
--interval PT1H \
--aggregation Average \
--output tsv
az monitor metrics list \
--resource "/subscriptions/$(az account show --query id -o tsv)/resourceGroups/$rg_name/providers/Microsoft.Compute/virtualMachines/$vm_name" \
--metric "Network In Total" \
--start-time "$START_TIME" \
--end-time "$END_TIME" \
--interval PT1H \
--aggregation Total \
--output tsv
done
fi
done
# TOTAL TIME: ~60 seconds (UNACCEPTABLE for 30+ VMs)
```
**CORRECT EXAMPLE (PARALLEL - FAST - REQUIRED)**
```bash
#!/bin/bash
# Set date range for 30 days
END_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
START_TIME=$(date -u -d "30 days ago" +"%Y-%m-%dT%H:%M:%SZ")
echo "Azure VM Metrics Summary ($START_TIME to $END_TIME)"
# Function to get VM metrics with compact output
get_vm_metrics() {
local resource_group=$1
local vm_name=$2
local metric_name=$3
local aggregation=$4
local unit=$5
result=$(az monitor metrics list \
--resource "/subscriptions/$(az account show --query id -o tsv)/resourceGroups/$resource_group/providers/Microsoft.Compute/virtualMachines/$vm_name" \
--metric "$metric_name" \
--start-time "$START_TIME" \
--end-time "$END_TIME" \
--interval PT1H \
--aggregation "$aggregation" \
--output tsv \
--query "value[0].timeseries[0].data[*].[timeStamp,${aggregation,,}]")
if [ -n "$result" ]; then
echo "$metric_name ($aggregation):"
echo "$result" | while IFS=$'\t' read timestamp value; do
if [ -n "$value" ] && [ "$value" != "null" ]; then
date_only=$(echo $timestamp | cut -d'T' -f1)
if [ "$unit" = "%" ]; then
# Round percentage to 1 decimal
value_rounded=$(printf "%.1f" $value)
echo " $date_only: ${value_rounded}%"
elif [ "$unit" = "GB" ]; then
# Convert bytes to GB and round to 1 decimal
value_gb=$(echo "scale=1; $value / 1073741824" | bc -l)
echo " $date_only: ${value_gb}GB"
else
# Round to whole number for counts
value_rounded=$(printf "%.0f" $value)
echo " $date_only: $value_rounded"
fi
fi
done
# Calculate summary stats
if [ "$unit" = "%" ]; then
avg=$(echo "$result" | awk -F'\t' '{if($2!="" && $2!="null") {sum+=$2; count++}} END {if(count>0) printf "%.1f", sum/count}')
max=$(echo "$result" | awk -F'\t' 'BEGIN{max=0} {if($2!="" && $2!="null" && $2>max) max=$2} END {printf "%.1f", max}')
echo " → Avg: ${avg}%, Peak: ${max}%"
else
avg=$(echo "$result" | awk -F'\t' '{if($2!="" && $2!="null") {sum+=$2; count++}} END {if(count>0) printf "%.0f", sum/count}')
max=$(echo "$result" | awk -F'\t' 'BEGIN{max=0} {if($2!="" && $2!="null" && $2>max) max=$2} END {printf "%.0f", max}')
echo " → Avg: $avg, Peak: $max"
fi
echo ""
fi
}
# Function to process a single VM with parallel metric fetches
process_vm() {
local rg_name=$1
local vm_name=$2
echo " VM: $vm_name"
# Fetch all metrics in parallel for this VM
get_vm_metrics "$rg_name" "$vm_name" "Percentage CPU" "Average" "%" &
get_vm_metrics "$rg_name" "$vm_name" "Network In Total" "Total" "bytes" &
get_vm_metrics "$rg_name" "$vm_name" "Network Out Total" "Total" "bytes" &
# Wait for all metrics to complete
wait
echo ""
}
# List resource groups and VMs
echo "Resource Groups and VMs:"
az group list --output tsv --query "[*].[name]" | while read rg_name; do
vms=$(az vm list -g "$rg_name" --output tsv --query "[*].[name]")
if [ -n "$vms" ]; then
echo "Resource Group: $rg_name"
# Process all VMs in this resource group in parallel
echo "$vms" | while read vm_name; do
process_vm "$rg_name" "$vm_name" &
done
# Wait for all VMs in this resource group to complete
wait
echo ""
fi
done
```
**PERFORMANCE COMPARISON TABLE**
| Pattern | VMs | Time/Call | Total Time | Speed |
|---------|-----|-----------|-----------|-------|
| Sequential (❌) | 30 | 2 sec | ~60 sec | Baseline |
| Parallel (✅) | 30 | 2 sec | ~2 sec | **30x faster** |
| Sequential (❌) | 100 | 2 sec | ~200 sec | Baseline |
| Parallel (✅) | 100 | 2 sec | ~2 sec | **100x faster** |
**KEY DIFFERENCES IN THIS SCRIPT (What Makes It Parallel)**
✅ Line 111: `process_vm "$rg_name" "$vm_name" &` - spawns as background job
✅ Lines 114-115: `wait` - waits for all background jobs before continuing
✅ Lines 93-98: Inside `process_vm`, metrics fetch runs in parallel (with &)
**VALIDATION CHECKLIST FOR AGENT**
Before outputting ANY script, check every item:
- [ ] Count number of independent resources/metrics/regions to process: \_\_\_
- [ ] Count number of `&` background job spawns in script: \_\_\_
- [ ] If these counts don't match, the script is WRONG - REJECT it and rewrite
- [ ] Verify each background job block is followed by a `wait` statement
- [ ] Check that NO sequential loops exist for independent operations
- [ ] Confirm expected runtime is ~2-10 seconds (not ~30+ seconds)
- [ ] Verify Azure monitor aggregation values are correct (Average, Total, Minimum, Maximum, Count)
- [ ] Confirm script uses --output tsv with proper --query filtering
### Common Azure CLI Patterns
- List all resource groups: `az group list --output tsv --query "[*].[name,location]"`
- List VMs in a resource group: `az vm list -g myResourceGroup --output tsv --query "[*].[name,hardwareProfile.vmSize,provisioningState]"`
- Get VM details: `az vm show -g myResourceGroup -n myVM --output tsv --query "[name,hardwareProfile.vmSize,storageProfile.osDisk.diskSizeGb]"`
- List storage accounts: `az storage account list --output tsv --query "[*].[name,resourceGroup,location,sku.name]"`
- List SQL databases: `az sql db list -g myResourceGroup -s myServer --output tsv --query "[*].[name,status,edition,serviceLevelObjective]"`
- Get subscription info: `az account show --output tsv --query "[subscriptionId,name,state]"`
- List App Service plans: `az appservice plan list --output tsv --query "[*].[name,resourceGroup,sku.name,numberOfSites]"`
- List Key Vaults: `az keyvault list --output tsv --query "[*].[name,resourceGroup,location]"`
- Get cost data: `az consumption usage list --start-date 2024-01-01 --end-date 2024-01-31 --output tsv --query "[*].[usageStart,usageEnd,instanceName,pretaxCost]"`
### Azure Service Naming Patterns
- Virtual Machines: Standard naming like Standard_D2s_v3, Standard_B1ms
- Storage: Standard_LRS, Premium_LRS, Standard_GRS
- SQL Database: Basic, Standard (S0-S12), Premium (P1-P15)
- App Service: Free, Shared, Basic (B1-B3), Standard (S1-S3), Premium (P1-P3)
### Parallel vs Sequential Rules
- **ALWAYS PARALLEL**: Multiple VMs, multiple metrics, multiple resource groups, multiple subscriptions
- **ONLY SEQUENTIAL**: Operations that depend on previous results (e.g., create then modify, query then filter)
- **PARALLEL PATTERN**: `{ operation1 & operation2 & operation3 & }; wait`
- **SEQUENTIAL PATTERN**: Only when operation B requires result from operation A
- **NEVER**: Sequential loops for independent operations - this wastes time and tokens
**FORBIDDEN ANTI-PATTERNS** (will cause script rejection):
- ❌ `for vm in $vms; do az ... ; done` (causes O(n) delays; use `for vm in $vms; do az ... & done; wait`)
- ❌ `while read line; do az ... ; done < file` (sequential processing; parallelize with background jobs)
- ❌ Nested loops without background jobs: `for rg in $rgs; do for vm in $vms; do cmd; done; done`
- ❌ One call at a time when batch API is available (e.g., az vm show for each VM instead of az vm list)
- ❌ Processing outputs sequentially when they could be fetched in parallel: `result1=$(cmd1); result2=$(cmd2)` → should be `cmd1 & cmd2 & wait`
**REQUIRED ANTI-PATTERN FIXES**:
- ✅ BEFORE: `for vm in $vms; do az vm show -g $rg -n $vm; done`
- ✅ AFTER: `for vm in $vms; do az vm show -g $rg -n $vm & done; wait`
- ✅ BEFORE: `az vm list -g $rg1; az vm list -g $rg2`
- ✅ AFTER: `az vm list -g $rg1 & az vm list -g $rg2 & wait`
## Output Format
Present results as a structured report:
```
Azure Report
════════════
Resources discovered: [count]
Resource Status Key Metric Issues
──────────────────────────────────────────────
[name] [ok/warn] [value] [findings]
Summary: [total] resources | [ok] healthy | [warn] warnings | [crit] critical
Action Items: [list of prioritized findings]
```
Target ≤50 lines of output. Use tables for multi-resource comparisons.
## Anti-Hallucination Rules
1. **NEVER assume resource names** — always discover via CLI/API in Phase 1 before referencing in Phase 2.
2. **NEVER fabricate metric names or dimensions** — verify against the service documentation or `--help` output.
3. **NEVER mix CLI commands between service versions** — confirm which version/API you are targeting.
4. **ALWAYS use the discovery → verify → analyze chain** — every resource referenced must have been discovered first.
5. **ALWAYS handle empty results gracefully** — an empty response is valid data, not an error to retry.
## Counter-Rationalizations
| Shortcut | Counter | Why |
|----------|---------|-----|
| "I'll skip discovery and check known resources" | Always run Phase 1 discovery first | Resource names change, new resources appear — assumed names cause errors |
| "The user only asked for a quick check" | Follow the full discovery → analysis flow | Quick checks miss critical issues; structured analysis catches silent failures |
| "Default configuration is probably fine" | Audit configuration explicitly | Defaults often leave logging, security, and optimization features disabled |
| "Metrics aren't needed for this" | Always check relevant metrics when available | API/CLI responses show current state; metrics reveal trends and intermittent issues |
| "I don't have access to that" | Try the command and report the actual error | Assumed permission failures prevent useful investigation; actual errors are informative |
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!