Skip to content

ManifestWriter.WriteManifest unexpectedly disposes the preserved manifest input stream #14881

Closed

Description

@dtivel

Summary

ManifestWriter.WriteManifest unexpectedly disposes the manifest's preserved input stream after writing.

This prevents callers from resetting, reading, retrying with, or otherwise reusing the stream. Writing the same manifest twice is one observable consequence, but the underlying regression is the change in stream ownership.

Regression

This behavior was introduced in d61038949d120d889624056a1be29292fd42c529 as part of #9983:

public static void WriteFile(string path, Stream stream)
{
-    StreamReader reader = new StreamReader(stream);
+    using StreamReader reader = new StreamReader(stream);
    WriteFile(path, reader.ReadToEnd());
}

StreamReader(Stream) defaults to leaveOpen: false. Disposing the reader therefore also disposes the supplied stream.

The affected call path is:

ManifestWriter.WriteManifest
  → Util.WriteTempFile(manifest.InputStream)
    → Util.WriteFile(path, manifest.InputStream)
      → StreamReader.Dispose()
        → manifest.InputStream.Dispose()

Expected behavior

Writing a manifest should not dispose its preserved input stream. ManifestWriter did not create that stream, and ownership is not transferred to Util.WriteFile.

After writing, callers should still be able to reset and read the stream:

Stream preservedInput = manifest.InputStream;

ManifestWriter.WriteManifest(manifest, firstOutputPath);

Assert.True(preservedInput.CanRead);

preservedInput.Position = 0;
using var reader = new StreamReader(
    preservedInput,
    Encoding.UTF8,
    detectEncodingFromByteOrderMarks: true,
    bufferSize: 1024,
    leaveOpen: true);

Assert.NotEmpty(reader.ReadToEnd());

Resetting the stream and writing another output should also remain possible:

preservedInput.Position = 0;
ManifestWriter.WriteManifest(manifest, secondOutputPath);

Actual behavior

After the first call to ManifestWriter.WriteManifest:

preservedInput.CanRead == false

Attempting to read or reuse it fails. A subsequent manifest write produces:

System.ArgumentException: Stream was not readable.
   at System.IO.StreamReader..ctor(Stream stream)
   at Microsoft.Build.Tasks.Deployment.ManifestUtilities.Util.WriteFile(...)
   at Microsoft.Build.Tasks.Deployment.ManifestUtilities.Util.WriteTempFile(...)
   at Microsoft.Build.Tasks.Deployment.ManifestUtilities.ManifestWriter.WriteManifest(...)

Affected versions

The regression is present in Microsoft.Build.Tasks.Core 17.11.x. The same reset-and-reuse scenario succeeds with 17.10.46.

The implementation on the current main branch still constructs the reader without leaveOpen: true.

Proposed fix

Leave the caller-owned stream open when disposing the temporary reader:

using var reader = new StreamReader(
    stream,
    Encoding.UTF8,
    detectEncodingFromByteOrderMarks: true,
    bufferSize: 1024,
    leaveOpen: true);

This is consistent with Util.WriteLogFile, which already preserves its supplied stream using leaveOpen: true.

Suggested tests

Add coverage that verifies:

  • ManifestWriter.WriteManifest leaves the preserved input stream readable;
  • the stream can be reset and read after writing;
  • the same manifest can be written again after resetting its input stream.

Activity

  1. changed the title [-]Preserve manifest input streams when writing temporary files[/-] [+]ManifestWriter.WriteManifest unexpectedly disposes the preserved manifest input stream[/+] on Aug 28, 2026
  2. added a commit that references this issue on Sep 9, 2026
    d58f871
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Priority:2Work that is important, but not critical for the releasebacklogtriaged

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions