Skip to content

Add Storage TSG: ghost CSV mount points (ClusterStorage.000 / .001 / .00X) - #344

Open
John Neemes (1008covingtonlane) wants to merge 6 commits into
Azure:mainfrom
1008covingtonlane:tsg-storage-ghost-csv-mount-points
Open

Add Storage TSG: ghost CSV mount points (ClusterStorage.000 / .001 / .00X)#344
John Neemes (1008covingtonlane) wants to merge 6 commits into
Azure:mainfrom
1008covingtonlane:tsg-storage-ghost-csv-mount-points

Conversation

@1008covingtonlane

Copy link
Copy Markdown
Collaborator

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 Cluster
Shared 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

  • No existing coverage. A sweep of the published TSG corpus and the internal
    engineering wiki returned nothing for this condition.
  • Not detectable centrally. Ghost roots are per-node filesystem state and are
    not carried in Azure Local observability pipelines, so this has to be an on-box
    procedure.
  • It recurs in the field. The condition shows up repeatedly across OEMs, often
    with a new numbered directory created after each solution update.

Structure

  • Quick triage that fans out to every node, explicitly labelled as a first look
    rather than a clearance.
  • Detection in three steps: find the roots, confirm which mount points are real,
    and check whether a ghost root still redirects to live storage.
  • Classification into three outcomes, each routing to one path.
  • Path A clean up when nothing references the roots.
  • Path B repoint a workload VM with Move-VMStorage.
  • Path C engage support when the reference is platform-managed content under
    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-VMStorage steps, revalidate, clean up, tear down, with
zero residue afterwards.

That run surfaced three defects, all now fixed in the guide:

  1. Move-VMStorage rejects the -Vhds hashtable when DestinationFilePath
    holds a PSObject-wrapped string returned by Join-Path. The error claims the
    key is missing when it is present, and the wrapper is invisible to normal
    checks: .GetType() reports String and -is [string] reports True. The
    guide now uses explicit [string] casts and explains why.
  2. Passing an empty -Vhds array fails the call, so the guide says to omit the
    parameter when only configuration or paging paths are affected.
  3. Windows -Filter 'ClusterStorage.*' also matches the real
    C:\ClusterStorage root. 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

  • All detection is read-only.
  • The pre-delete gate calls the same audit function as the classification
    step, so it cannot be narrower than the check it claims to reconfirm.
  • The delete block refuses to recurse into reparse points and requires a typed
    confirmation rather than relying on the operator removing -WhatIf.
  • Move-VMStorage is dry-run with -WhatIf first, and the destination is
    validated against the live CSV list before anything moves.

Review notes

  • Structure and safety lint: A. All 24 PowerShell blocks parse clean and all
    15 in-page anchors resolve.
  • Reviewed through deep-systems, accessibility, and literal-follower lenses across
    multiple model families; the resulting fixes include the glossary, the elevated
    PowerShell prerequisite, the reparse-point attribute check replacing an
    unreliable LinkType test, and the note that per-node Get-VM cannot see
    clustered VMs owned by other nodes.

….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 AI balanced review requested due to automatic review settings August 18, 2026 13:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 $vhds to 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, add Vhds only 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 to Remove-Item -Recurse, despite the block's promise to refuse reparse points. How: test each $t.Attributes first 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.

Comment thread TSG/Storage/Troubleshoot-Storage-GhostCsvMountPoints.md
Comment thread TSG/Storage/Troubleshoot-Storage-GhostCsvMountPoints.md Outdated
Comment thread TSG/Storage/Troubleshoot-Storage-GhostCsvMountPoints.md
…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.
@Karl-WE

Copy link
Copy Markdown

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants