Add Storage TSG: ghost CSV mount points (ClusterStorage.000 / .001 / .00X) - #344
Open
John Neemes (1008covingtonlane) wants to merge 7 commits into
Open
Conversation
….00X) Azure Local and Windows Server failover clusters can leave numbered directories such as C:\ClusterStorage.000 beside the real CSV root. They are usually harmless remnants, but when a VM, cluster resource, or platform component still references one, solution updates and Arc Resource Bridge operations fail with errors that never mention a folder name. Nothing in the public TSG corpus covered this, and it is not detectable from fleet telemetry because ghost roots are per-node filesystem state. The guide covers detection, classification, and three remediation paths: clean up when unreferenced, repoint a workload VM with Move-VMStorage, or engage support when the reference is platform-managed content under Infrastructure_1. Root cause is cited to the Microsoft Windows Support Team article on CSV path initialization failing under an open handle, with antivirus and filter drivers named as the common trigger. Prevention points at the documented Hyper-V antivirus exclusions for C:\ClusterStorage. Validated end to end on a lab cluster (inject, detect, mitigate, revalidate, cleanup, teardown, zero residue). That run surfaced three defects now fixed in the guide: * Move-VMStorage rejects the -Vhds hashtable when DestinationFilePath holds a PSObject-wrapped string from Join-Path, reporting a misleading "must contain 'DestinationFilePath' key" error even though the key is present. The guide uses explicit [string] casts and explains why. * Passing an empty -Vhds array makes the call fail, so the guide says to omit the parameter when only config or paging paths are affected. * Windows -Filter 'ClusterStorage.*' also matches the real C:\ClusterStorage root, so every block pairs it with a ^ClusterStorage\.\d+$ guard and the guide warns against simplifying it. Safety: all detection is read-only, the deletion gate reuses the same audit function as the classification step, refuses to recurse into reparse points, and requires a typed confirmation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8d31ddd7-9fbb-4ff0-8aa7-3f9d88060ed7
Copilot started reviewing on behalf of
John Neemes (1008covingtonlane)
August 18, 2026 13:05
View session
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a Storage troubleshooting guide for detecting, classifying, remediating, and preventing ghost CSV mount points.
Changes:
- Adds cluster-wide detection and safety checks.
- Documents cleanup, VM migration, support escalation, and verification paths.
- Adds the guide to the Storage index.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
TSG/Storage/Troubleshoot-Storage-GhostCsvMountPoints.md |
Adds the troubleshooting guide and PowerShell procedures. |
TSG/Storage/README.md |
Links the new guide from the Storage index. |
Suppressed comments (2)
TSG/Storage/Troubleshoot-Storage-GhostCsvMountPoints.md:848
- What: both runnable examples always pass
-Vhds $vhds, although the preceding branch explicitly allows$vhdsto be empty. Why: in the configuration/checkpoint/paging-only case, the guide states that this call is rejected, so both the dry run and real move fail unless the reader manually rewrites the command. How: build a parameter hashtable, addVhdsonly when$vhds.Count -gt 0, and use the same splat for both calls.
Move-VMStorage -Name $VMName `
-VirtualMachinePath $Destination `
-SnapshotFilePath $Destination `
-SmartPagingFilePath $Destination `
-Vhds $vhds `
-WhatIf
TSG/Storage/Troubleshoot-Storage-GhostCsvMountPoints.md:710
- What: the safety scan checks descendants but never checks whether a target root itself is a reparse point. Why: a matching
C:\ClusterStorage.<digits>junction can pass this gate and then be sent toRemove-Item -Recurse, despite the block's promise to refuse reparse points. How: test each$t.Attributesfirst and skip descendant traversal when the root is a reparse point.
$unsafe = foreach ($t in $targets) {
Get-ChildItem -LiteralPath $t.FullName -Force -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.Attributes -band [System.IO.FileAttributes]::ReparsePoint }
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…quired The OEM field-engineer review lens was the only reviewer scoring below 5. It asked for an explicit statement in At a glance so a vendor engineer can hand the case back without reading the whole guide. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8d31ddd7-9fbb-4ff0-8aa7-3f9d88060ed7
Three fixes for the review bot's findings: 1. Get-GhostCsvAudit fail-CLOSED (was fail-open). SafeToDelete previously depended only on an empty findings list while every query used -ErrorAction SilentlyContinue and cluster-node discovery silently fell back to the local machine, so an unreachable node or failed cmdlet could report SafeToDelete = True without cluster-wide clearance. Now every discovery/query/remoting error is recorded as a blocker, cluster-node discovery failure is a blocker (no local-machine substitution), and every expected running node must return a result or it is a blocker. 2. Root-cause rationale now anchors on Microsoft Learn (Recommended antivirus exclusions for Hyper-V hosts; Events 5120/5142 unable to access the ClusterStorage folder) instead of a non-Microsoft blog. The community blog is retained as an explicitly non-normative secondary reference. 3. Move-VMStorage disk mapping gives each disk a unique per-disk subdirectory, so two disks that share a leaf filename no longer collide on an identical DestinationFilePath (which made Move-VMStorage fail). All 24 PowerShell blocks parse clean (PSParser AST).
1. Get-ClusterParameter error handling made consistent across the three detection/evidence/verify blocks (2C, the Path C evidence collector, and the "Verify the fix" resource check). They previously swallowed the error silently. They now record each resource whose parameters could not be read and surface a summary (a warning for the console checks, and a companion cluster-resource-uninspected.txt for the support-evidence bundle), so a genuine query failure no longer silently reduces coverage. These remain non-fatal by design (unlike Get-GhostCsvAudit, which is the destructive gate and blocks on any error): a resource type that legitimately exposes no parameters is benign and expected. 2. Separated the per-disk-subdirectory rationale from the [string]-cast note in the Move-VMStorage mapping block so the two comments read distinctly. All 24 PowerShell blocks parse clean (PSParser AST).
…stination hole Applied from a tsg-forge grading run (live L3 loop plus a negative safety test on a lab cluster, and a multi-model persona panel across two frontier model families). Data-loss path (deep-systems lens): - Step 2A read only the attached disk path, so a checkpoint or differencing PARENT sitting on a ghost root was invisible to the audit and would then be deleted by Path A, breaking the chain. Step 2A now walks the full parent chain with Get-VHD, and the Path A audit walks it too, so an unreadable link is a blocker rather than a silent pass. This is the hazard the guide's own Root cause section describes, so the guide previously contradicted itself. Safety (literal-follower lens): - Path B validated the destination only as "an active CSV", but Get-ClusterSharedVolume also returns the reserved Infrastructure_1 volume, which the guide's own Path C says is off limits. The destination check now rejects the infrastructure volume first, with its own specific error, and no longer offers it in the list of valid values. - The -Vhds note told the reader to hand-edit the command when the array was empty. The move now uses a splatted parameter set that includes -Vhds only when there is something to move, removing the manual edit. Live validation on a lab cluster confirmed the detection, the fail-closed audit, and the Path A cleanup, and confirmed that a reparse point inside a ghost root correctly blocks the audit and refuses the delete while the data behind the junction survives.
|
thank you for the update to the TSG documentation. Is there any signal this root issue will be fixed? (RCA) because it happens with every AzL update. |
Ran the detection read-only against a live 3-node Azure Local cluster. Every node
had seven numbered roots (.000 through .006) accumulated between 2025-11 and
2026-07, one per update, with zero references from any VM disk, VM configuration,
cluster resource parameter, or SMB open file, and no active CSV under a numbered
root.
Each ghost root held exactly one child: a stale Infrastructure_1 breadcrumb of
9 items / 2 files / 528 bytes, at
Infrastructure_1\Shares\SU1_Infrastructure_1\Orchestration\
AgentLifecycleManagement\FCARotation\SuccessFiles
which matches the path recorded in the resolved IcM for this phenomenon.
The guide as written would have routed all seven to Path C and told the operator
to open a support case, purely because the content sits under Infrastructure_1.
That is over-escalation on what is the most common shape in the field, and it is
exactly the alarm fatigue that leads people to ignore the check. The companion
CSSTools analyzer already had the better rule: platform content only matters when
something still references it.
Aligned the guide with that rule:
* Step 2D now distinguishes referenced platform content, and ARB/MOC working data
such as a .vhdx under MocArb\WorkingDirectory\ or an ImageStore folder, from
small stale orchestration breadcrumbs with no references.
* The Step 3 classification and Path C entry conditions were narrowed to match,
with an explicit note that an Infrastructure_1 folder inside a ghost root is not
by itself a support case.
* Symptoms now describes the accumulate-one-per-update shape, since that is what
an operator is most likely to actually see.
The safety properties are unchanged: any reference, any reparse point, any ARB or
MOC working data, or an active CSV under a numbered root still routes to Path C.
Lint remains A, 16 anchors resolve, no prose violations.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8d31ddd7-9fbb-4ff0-8aa7-3f9d88060ed7
…g delete Third-model review round (xAI added to the panel) found three real defects, including two in the hardening from the previous round. All are now fixed and re-validated on a live lab cluster. - Step 1C tested only the immediate children of a ghost root for a reparse point. A volume mount nested inside an ordinary-looking leftover folder therefore classified as "ordinary files", routed to Path A, and would have been deleted as live storage. The scan is now recursive, with the reason stated. - The pre-delete safety scan used -ErrorAction SilentlyContinue, so an unreadable subtree could hide the very reparse point the gate exists to find. It now uses -ErrorAction Stop and treats an enumeration failure as unsafe, and it re-checks each root immediately before removing it. - The delete used Remove-Item -Recurse, which on Windows PowerShell 5.1 does not guarantee it will not follow a junction. It now uses [System.IO.Directory]::Delete, which removes a reparse point as a link. - The parent-chain walk added last round failed open at depth 0: an unreadable attached disk recorded no blocker, so SafeToDelete could still be true with no parent-chain coverage. It now fails closed at every depth, and also blocks when a chain exceeds the walk limit. - The DELETE confirmation is now stated as case-sensitive. Re-validated live with a junction placed one level down inside a ghost root: the cleanup correctly refused, the data behind the junction survived, and after the junction was removed as a link the root was removed cleanly with the live CSV root intact.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
A new Storage TSG for ghost CSV mount points: the numbered directories
(
C:\ClusterStorage.000,.001,.00X) that appear beside the real ClusterShared Volume root.
They are usually harmless remnants. They become an incident when a VM, a cluster
resource, or an Azure Local platform component still references one, because
solution updates and Arc Resource Bridge operations then fail with errors that
never mention a folder name.
Why now
engineering wiki returned nothing for this condition.
not carried in Azure Local observability pipelines, so this has to be an on-box
procedure.
with a new numbered directory created after each solution update.
Structure
rather than a clearance.
and check whether a ghost root still redirects to live storage.
Move-VMStorage.Infrastructure_1, which is outside the customer support boundary.Root cause and prevention
Root cause is attributed to CSV path initialization failing while another process
holds an open handle, with antivirus and filter drivers as the common trigger,
cited to the Microsoft Windows Support Team article on the behavior. Prevention
points at the documented Hyper-V antivirus exclusions for
C:\ClusterStorage,which is the actual fix for recurrence.
The guide deliberately does not claim a specific Event ID for the rename,
because none could be confirmed. It notes that Events 5120 and 5142 are related
CSV I/O events but are not this record, so neither their presence nor their
absence proves anything here.
Validation
Run end to end on a lab cluster: baseline clean, inject, detect, mitigate using
the guide's own
Move-VMStoragesteps, revalidate, clean up, tear down, withzero residue afterwards.
That run surfaced three defects, all now fixed in the guide:
Move-VMStoragerejects the-Vhdshashtable whenDestinationFilePathholds a
PSObject-wrapped string returned byJoin-Path. The error claims thekey is missing when it is present, and the wrapper is invisible to normal
checks:
.GetType()reportsStringand-is [string]reportsTrue. Theguide now uses explicit
[string]casts and explains why.-Vhdsarray fails the call, so the guide says to omit theparameter when only configuration or paging paths are affected.
-Filter 'ClusterStorage.*'also matches the realC:\ClusterStorageroot. Every block pairs the filter with a^ClusterStorage\.\d+$guard, and the guide warns against simplifying it,since feeding an unguarded filter into a delete command would target the live
CSV root.
Safety
step, so it cannot be narrower than the check it claims to reconfirm.
confirmation rather than relying on the operator removing
-WhatIf.Move-VMStorageis dry-run with-WhatIffirst, and the destination isvalidated against the live CSV list before anything moves.
Review notes
15 in-page anchors resolve.
multiple model families; the resulting fixes include the glossary, the elevated
PowerShell prerequisite, the reparse-point attribute check replacing an
unreliable
LinkTypetest, and the note that per-nodeGet-VMcannot seeclustered VMs owned by other nodes.