Hot-tier blob data that nothing has touched
Blob data held in the Hot access tier on an account that recorded no transactions at all over the window, where the Cool tier would bill less for the same bytes.
Why it happens
Hot-tier blob capacity bills at a higher per-GB rate than Cool in exchange for cheaper access, so data nobody reads is paying for an access discount it never uses. The rule fires only when transactions across the window are exactly zero, because the trade runs the other way as soon as the data is read: on LRS the Hot-to-Cool saving is about $0.0056 per GB per month while reading a GB back from Cool costs about $0.01, so a single full read a month makes Cool the more expensive tier. Zero transactions is the one condition under which the saving is real rather than a bet on future access. Premium block blob accounts are excluded because Azure publishes no Cool capacity meter for them, so there is nothing to move to.
Paste this into Resource Graph Explorer in the Azure portal.
resources
| where type =~ 'microsoft.storage/storageaccounts'
// Scope below matches CloudFinOpsPro policy DEFAULTS: exclusions.protectTag, environment.tagKeys,
// environment.allowedTagValues, and allowUntagged false so an untagged resource counts as production.
// If your policy.yaml differs, edit the three lines below or this will disagree with your own agent.
| where tolower(tostring(coalesce(tags['cfop:protect'], ''))) !in ('true', 'yes', '1')
| extend cfopEnv = tolower(trim(' ', tostring(coalesce(tags['Environment'], tags['environment'], tags['env'], ''))))
| where cfopEnv in ('dev', 'development', 'test', 'qa', 'sandbox', 'nonprod', 'non-prod', 'staging')
| where tostring(properties.accessTier) =~ 'Hot'
| project account = name, resourceGroup, location,
sku = tostring(sku.name),
acctKind = tostring(kind)Resource Graph shows the account's default tier, not the tier of each blob, and it cannot show when a blob was last read. The lifecycle management policy is deliberately not projected: it is a sub-resource that the accounts row does not carry, so isnotnull(properties.managementPolicy) reads false on every account and would suggest no rules exist when it only means Resource Graph cannot see them. Confirm with the Azure Monitor BlobCapacity metric split by BlobTier before moving anything, because reading data back from Cool or Archive carries a retrieval charge that can exceed the saving.
Applies to microsoft.storage/storageaccountsWhat the agent does about it
Blob data held in the Hot access tier on an account that recorded no transactions at all over the window, where the Cool tier would bill less for the same bytes.
It will not fix this one
It reports this and takes no action. A check only earns the right to change something once the action behind it can re-read live state before it fires and knows the way back.
Related checks
Storage account with SFTP enabled but no SFTP activity
A storage account with `properties.isSftpEnabled == true` that recorded no transactions of any kind in the evaluation window, so its hourly SFTP charge is buying nothing.
Geo-redundant storage account outside production
A storage account replicated to a second region. Geo-redundancy roughly doubles the per-GB storage rate against locally redundant storage, and read-access variants cost more again.
Expired or orphaned snapshot
A snapshot past its retention window whose source disk no longer exists.