Help us improve
Share bugs, ideas, or general feedback.
From amg-toolkit
Fleet-wide Azure Storage Account health check — pulse check for availability, latency, transactions, and error rates across all accounts, then deep-dives into the top 7 most interesting accounts with metrics (E2E latency, server latency, capacity, ingress/egress) and resource logs. Tracks known issues across sessions via persistent report. On first run, auto-discovers datasource UID and prompts for subscription ID.
npx claudepluginhub azure/amg-skills --plugin amg-toolkitHow this skill is triggered — by the user, by Claude, or both
Slash command
/amg-toolkit:amg-check-storage-accountThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
- Current UTC time: !`date -u +%Y-%m-%dT%H:%M:%SZ`
Audits Azure Storage accounts for misconfigurations: public blob access, missing encryption, permissive SAS tokens, disabled logging, and network violations using Azure CLI, PowerShell, and Defender for Storage.
Audits Azure Storage accounts for misconfigurations: public blob access, missing encryption, permissive SAS tokens, disabled logging, and network violations using Azure CLI, PowerShell, and Defender for Storage.
Detects Azure storage account misconfigurations including public blob containers, missing encryption, broad SAS tokens, disabled logging, and network access violations using Azure CLI, PowerShell, and Microsoft Defender for Storage. Use for security audits, compliance, or data exposure investigations.
Share bugs, ideas, or general feedback.
date -u +%Y-%m-%dT%H:%M:%SZcat memory/amg-check-storage-account/config.md 2>/dev/null || echo "NOT_CONFIGURED"[ -f memory/amg-check-storage-account/report.md ] && echo "exists ($(grep -c '^### SA-' memory/amg-check-storage-account/report.md) bugs documented)" || echo "not found"Known Issues: Before presenting findings, cross-reference results against
memory/amg-check-storage-account/report.md.
Analyze Azure Storage Account health using a two-phase approach: a single amgmcp_pulse_check call for fleet-wide summary, followed by targeted deep dives into the top 7 most interesting accounts only.
from/to — NEVER use timespan (it causes errors).PT1H — do NOT use PT6H or P1D for this metric.allowed-tools frontmatter references the MCP server name — update it if your server has a different name)If Config shows NOT_CONFIGURED: Run First-Run Setup at the bottom of this file, then return here.
If Config is populated: Extract the datasource UID and subscription ID(s) from the pre-loaded Runtime Context above and use them for all queries. Use $1 as the subscription override if provided.
## Azure Monitor Datasource > UID## Subscriptions (or $1 if provided)microsoft.storage/storageaccounts (lowercase)/subscriptions/{SUB}/resourceGroups/{RG}/providers/Microsoft.Storage/storageAccounts/{name}Default is 7 days (pastDays: 7) for pulse check and deep-dive metrics, 24 hours for resource logs. If the user specifies a different range via $ARGUMENTS[0] (e.g., /amg-check-storage-account 3d), adjust accordingly. For resource log queries, keep the range narrow (1-2 days) to avoid timeouts.
Step 1a: Validate Datasource
Call amgmcp_datasource_list with no parameters.
Search the results for a datasource with type equal to grafana-azure-monitor-datasource. Extract its uid.
memory/amg-check-storage-account/config.md, warn the user, and use the new UID.Step 1b: Discover All Storage Accounts
Call amgmcp_query_resource_graph once using the configured datasource UID and subscription ID(s):
azureMonitorDatasourceUid: {DATASOURCE_UID}
query: |
resources
| where type == 'microsoft.storage/storageaccounts'
| where subscriptionId in ({SUBSCRIPTION_IDS})
| project name, resourceGroup, location, subscriptionId, properties.provisioningState
| order by location asc, name asc
Replace {SUBSCRIPTION_IDS} with the configured subscription IDs formatted as comma-separated quoted strings (e.g., 'sub-id-1', 'sub-id-2').
Constructing the ARM resource ID: Use subscriptionId from each row:
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Storage/storageAccounts/{name}
Region summary: Derive from the account list by counting accounts per unique location value.
Note any accounts not in "Succeeded" provisioning state — flag them immediately.
If zero accounts are found, report "No Storage Accounts found" and stop.
Call amgmcp_pulse_check once to get a summary across all storage accounts:
azureMonitorDatasourceUid: {DATASOURCE_UID}
pastDays: 7
scenarios: storage_summary
If $1 provides a subscription ID, add subscriptionId to scope the scan. Otherwise, if the config has a single subscription, pass it.
After the pulse check, verify:
status: "completed".Cross-reference pulse check results with Phase 1 inventory to enrich each account with its resource group and region from the Resource Graph data.
From the pulse check results, select at most 7 accounts for detailed investigation. Prioritize accounts with the most interesting signals:
If the pulse check shows fewer than 7 accounts with notable signals, only deep-dive those that have something worth investigating. Do not pad to 7.
If the pulse check shows the entire fleet is healthy with no notable signals, skip Phase 3 entirely and report the fleet as healthy.
For each selected account, query these metrics in parallel using amgmcp_query_resource_metric. Compute from (matching pastDays from Phase 2) and to (now) in ISO 8601 UTC.
| Metric Name | Aggregation | Interval | Purpose |
|---|---|---|---|
Availability | Average | PT6H | Availability trend |
SuccessE2ELatency | Average | PT6H | Client-perceived latency |
SuccessE2ELatency | Maximum | PT6H | Tail latency spikes |
SuccessServerLatency | Average | PT6H | Storage backend latency |
Transactions | Total | PT6H | Total volume (no filter) |
Transactions (errors) | Total | PT6H | Error count (filter: ResponseType ne 'Success') |
UsedCapacity | Average | PT1H | Current capacity (use last data point) |
Ingress | Total | PT6H | Data volume in |
Egress | Total | PT6H | Data volume out |
All 7 accounts can be queried in parallel (7 accounts x 9 metrics = 63 calls, within the 30-call batch cap when split into 3 batches).
Correlation analysis — when analyzing metrics together:
For each selected account, query Storage resource logs using amgmcp_query_resource_log. Keep time range to 1-2 days.
Log Query 1: Failed requests by status code
StorageBlobLogs
| where TimeGenerated between (datetime(<START>) .. datetime(<END>))
| where StatusCode >= 400
| summarize count() by StatusCode, StatusText, bin(TimeGenerated, 1h)
| order by TimeGenerated asc
Log Query 2: High latency operations
StorageBlobLogs
| where TimeGenerated between (datetime(<START>) .. datetime(<END>))
| where ServerLatencyMs > 100
| summarize count(), avg(ServerLatencyMs), max(ServerLatencyMs) by OperationName
| order by count_ desc
| take 20
Log Query 3: Error distribution by operation
StorageBlobLogs
| where TimeGenerated between (datetime(<START>) .. datetime(<END>))
| where StatusCode >= 400
| summarize count() by OperationName, StatusCode, StatusText
| order by count_ desc
| take 30
Log Query 4: Request volume trend
StorageBlobLogs
| where TimeGenerated between (datetime(<START>) .. datetime(<END>))
| summarize
TotalRequests=count(),
FailedRequests=countif(StatusCode >= 400),
ThrottledRequests=countif(StatusCode == 503),
AvgLatencyMs=round(avg(ServerLatencyMs), 2)
by bin(TimeGenerated, 1h)
| order by TimeGenerated asc
Log Query 5: Authentication failures
StorageBlobLogs
| where TimeGenerated between (datetime(<START>) .. datetime(<END>))
| where StatusCode == 403
| summarize count() by AuthenticationType, CallerIpAddress, UserAgentHeader
| order by count_ desc
| take 20
Note: If
StorageBlobLogsreturns no data, diagnostic settings may not be configured. TryStorageTableLogsorStorageQueueLogs. If none return data, note it and skip logs for that account.
| Severity | Criteria |
|---|---|
| CRITICAL | Availability avg < 99.0% |
| WARNING | Availability avg < 99.9%, OR SuccessE2ELatency avg > 50ms, OR sustained latency > 20ms for 6+ hours, OR sustained error transactions across multiple time windows |
| DORMANT | All metrics return empty timeSeries (no traffic in scan period) |
| HEALTHY | All metrics within normal ranges |
For known patterns, deep-dive queries, and correlation techniques, see reference/analysis-patterns.md.
For optional deep-dive queries, see reference/deep-dive-queries.md.
Present a summary report with these sections:
Account count by region and subscription. Flag any accounts not in "Succeeded" provisioning state.
Fleet-wide summary from the storage_summary pulse check:
For each selected account:
For each deep-dived account:
Compare findings against memory/amg-check-storage-account/report.md. For each known bug, state: still active / improving / worsening / resolved.
Prioritized list:
After presenting findings, update memory/amg-check-storage-account/report.md:
Only add genuine issues: sustained availability drops, persistent throttling, high error rates, or latency degradation.
See ${CLAUDE_SKILL_DIR}/reference/error-handling.md for the full recovery table.
Microsoft.Storage/storageAccounts/subscriptions/{SUB}/resourceGroups/{RG}/providers/Microsoft.Storage/storageAccounts/{name}StorageBlobLogs (primary), StorageTableLogs, StorageQueueLogs (alternatives)503 (throttling), 403 (auth failure), 404 (not found), 409 (conflict)PT1H interval onlymemory/amg-check-storage-account/report.mdmemory/amg-check-storage-account/config.mdRun only when Config shows NOT_CONFIGURED. After completing, return to the Workflow above.
1. Discover Datasource UID: Call amgmcp_datasource_list. Filter type == "grafana-azure-monitor-datasource". Prefer uid == "azure-monitor-oob" if multiple match. Abort if zero match.
2. Discover Subscription ID(s): Run this Resource Graph query to list all subscriptions with storage accounts, then present the results as a table and ask the user which subscription(s) to use:
resources
| where type == 'microsoft.storage/storageaccounts'
| join kind=inner (
resourcecontainers
| where type == 'microsoft.resources/subscriptions'
| project subscriptionId, subscriptionName=name
) on subscriptionId
| summarize StorageAccounts=count() by subscriptionId, subscriptionName
| order by StorageAccounts desc
Present the results as a table with columns: Subscription Name, Subscription ID, Storage Accounts. Then ask the user: "Which subscription ID(s) should I configure for this health check?"
3. Write config: Write memory/amg-check-storage-account/config.md:
# amg-check-storage-account Configuration
User-specific values for the Storage Account health check skill.
This file is auto-generated on first run and can be edited manually.
## Azure Monitor Datasource
- **UID**: {discovered_uid}
- **Name**: {discovered_name}
## Subscriptions
- {subscription_id_1}
- {subscription_id_2}
4. Confirm: Show the resolved config and ask for confirmation before proceeding.