microsoft/aspire · error · DistributedApplicationException

The filesystem spelling of

Error message

The filesystem spelling of '{originalPath}' could not be matched in '{current}'.

What it means

ResolveFilesystemCasing tries to match each path segment against the actual directory entries: first exactly (ordinal), then by Unicode Form-C-normalized comparison ignoring case. If no entry matches either way, the path segment does not exist on disk as spelled, and this exception is thrown so the Dockerfile never references a nonexistent file.

Solutions

  1. Correct the manifest/source path spelling to exactly match the on-disk name (check case and accents with ls).
  2. Rename the file on disk to the expected spelling (git mv Main.rs main.rs).
  3. Normalize filenames to NFC (e.g. macOS: ditto or a normalization script) when moving between APFS/HFS+ and Linux.
  4. Re-check that the segment actually exists — it may have been deleted.

Example fix

// before
var path = "/app/SRC/main.rs"; // disk has src/
// after
var path = "/app/src/main.rs"; // matches on-disk spelling exactly
Defensive patterns

Strategy: validation

Validate before calling

var fullPath = Path.GetFullPath(manifestPath);
var segments = fullPath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
foreach (var seg in segments)
    if (seg is not ("/" or "~") && !Directory.EnumerateFileSystemEntries(Path.GetDirectoryName(fullPath)!, Path.GetFileName(fullPath)).Any())
        throw new FileNotFoundException($"Segment '{seg}' not found with exact spelling");

Try / catch

try { PublishAsync(); }
catch (DistributedApplicationException ex) when (ex.Message.Contains("could not be matched")) {
    logger.LogError("A path segment spelling does not exist on disk: {Msg}", ex.Message);
}

Prevention

When it happens

Trigger: WithRustApp / publish-mode generation with a DockerfileBuildAnnotation sourcePath or manifest path containing a segment whose spelling (case or Unicode normalization) does not exist in the parent directory — e.g. path says 'Main.rs' but disk has 'main.rs' on a case-sensitive FS, or a decomposed (NFD) macOS name matched against an NFC string that truly differs.

Common situations: Path built by string concatenation with wrong casing; files authored on macOS (NFD) deployed to case-sensitive Linux containers; typos after renaming files; file deleted after the path was configured.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/28be546bf3d69da3. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.Rust/RustDockerfileGenerator.cs:314

            }
            catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
            {
                throw new DistributedApplicationException(
                    $"The filesystem spelling of '{originalPath}' could not be read from '{current}'.",
                    ex);
            }

            // APFS can treat canonically equivalent Unicode names as the same entry while returning the
            // stored normalization form. Normalize only for matching, then keep the enumerated spelling.
            var normalizedSegment = segments[i].Normalize(NormalizationForm.FormC);
            current = entries.FirstOrDefault(entry =>
                    string.Equals(Path.GetFileName(entry), segments[i], StringComparison.Ordinal))
                ?? entries.FirstOrDefault(entry =>
                    string.Equals(
                        Path.GetFileName(entry).Normalize(NormalizationForm.FormC),
                        normalizedSegment,
                        StringComparison.OrdinalIgnoreCase))
                ?? throw new DistributedApplicationException(
                    $"The filesystem spelling of '{originalPath}' could not be matched in '{current}'.");
        }

        return current;
    }

    private static string BuildCargoCommand(List<string> cargoArgs)
        => string.Join(" ", new[] { "cargo", "build" }.Concat(cargoArgs.Select(ShellQuote)));

    private static void ValidateCargoArgumentsDoNotContainCredentials(IReadOnlyList<string> cargoArgs, string resourceName)
    {
        for (var i = 0; i < cargoArgs.Count; i++)
        {
            string? configuration = null;
            if (cargoArgs[i] == "--config" && i + 1 < cargoArgs.Count)
            {
                configuration = cargoArgs[++i];
            }

View on GitHub (pinned to 25830f84bd)