microsoft/aspire · error · InvalidOperationException

Integration closure metadata line

Error message

Integration closure metadata line '{line}' is invalid.

What it means

Each line of the closure metadata manifest must contain exactly 4 pipe-separated fields (artifact name, version, etc., parsed with Split('|', 4)). A line with fewer than 4 fields is structurally invalid — likely truncated or from an incompatible manifest format — so the parser throws InvalidOperationException quoting the offending line. Values are additionally normalized after the shape check.

Solutions

  1. Inspect the quoted line in the metadata manifest and restore the missing pipe-separated fields (expected shape: field1|field2|field3|field4).
  2. Regenerate the closure metadata by rebuilding the prebuilt AppHost instead of editing it by hand.
  3. Align CLI/bundle versions so the writer and parser use the same metadata line format.
  4. Escape or re-check how the writer serializes fields if package identifiers can contain '|'.

Example fix

// before (invalid, 3 fields)
Newtonsoft.Json|13.0.3|sha256-abc123
// after (4 fields)
Newtonsoft.Json|13.0.3|lib/net6.0/Newtonsoft.Json.dll|sha256-abc123
Defensive patterns

Strategy: validation

Validate before calling

static bool IsValidMetadataLine(string line) =>
    !string.IsNullOrWhiteSpace(line) && line.Split('|', 4).Length == 4;

Type guard

static bool TryParseClosureMetadata(string line, out string[] parts) { parts = line.Split('|', 4); return parts.Length == 4; }

Try / catch

try { metadata = ParseClosureMetadata(line); }
catch (InvalidOperationException ex) when (ex.Message.Contains("metadata line"))
{ logger.LogError(ex, "Skipping corrupt metadata line — regenerate manifest"); }

Prevention

When it happens

Trigger: Parsing a metadata manifest line that contains fewer than 4 '|'-delimited segments, e.g. a blank-ish line that escaped the empty-line filter, a manually edited line, or a line written by an older/newer format with 3 fields.

Common situations: Hand-editing the metadata file and dropping a field; a package name or path containing '|'-related corruption; version skew where the manifest was written by a different CLI version using a different field count; partial file write leaving a truncated last line.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at src/Aspire.Cli/Projects/PrebuiltAppHostServer.cs:1570

    private static async Task<List<string>> ReadManifestFileAsync(string filePath, CancellationToken cancellationToken)
    {
        if (!File.Exists(filePath))
        {
            throw new InvalidOperationException($"Integration closure manifest file '{filePath}' was not found after build.");
        }

        var lines = await File.ReadAllLinesAsync(filePath, cancellationToken).ConfigureAwait(false);
        return lines.Where(static line => !string.IsNullOrWhiteSpace(line)).Select(static line => line.Trim()).ToList();
    }

    private static ClosureMetadata ParseClosureMetadata(string line)
    {
        ArgumentNullException.ThrowIfNull(line);

        var parts = line.Split('|', 4);
        if (parts.Length != 4)
        {
            throw new InvalidOperationException($"Integration closure metadata line '{line}' is invalid.");
        }

        return new ClosureMetadata(
            NormalizeClosureMetadataValue(parts[0]),
            NormalizeClosureMetadataValue(parts[1]),
            NormalizeClosureMetadataValue(parts[2]),
            NormalizeClosureMetadataValue(parts[3]));
    }

    private static string? NormalizeClosureMetadataValue(string value)
    {
        return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
    }

    private static async Task<Dictionary<string, string>> ReadPackageFingerprintsAsync(string assetsFilePath, CancellationToken cancellationToken)
    {
        if (!File.Exists(assetsFilePath))
        {

View on GitHub (pinned to 25830f84bd)