Skip to content

Stream action archive downloads to disk instead of buffering in memory - #4588

Open
benoit-nexthop wants to merge 1 commit into
actions:mainfrom
benoit-nexthop:fix-action-download-buffering
Open

Stream action archive downloads to disk instead of buffering in memory#4588
benoit-nexthop wants to merge 1 commit into
actions:mainfrom
benoit-nexthop:fix-action-download-buffering

Conversation

@benoit-nexthop

Copy link
Copy Markdown

Fixes #4587

DownloadRepositoryArchive calls HttpClient.GetAsync(downloadUrl) with the default HttpCompletionOption.ResponseContentRead, which buffers the entire archive in the managed heap before ReadAsStreamAsync() returns — so the CopyToAsync into the FileStream copies from memory, not from the network. Peak worker memory grows by the full archive size per download (~73 MB in our case for an action hosted in a monorepo, since the codeload tarball is a snapshot of the whole source tree), and the retry loop re-buffers the entire body on each attempt. On memory-constrained self-hosted runners this throws System.OutOfMemoryException during the "Download action repository" phase (full details and job logs in #4587).

This change passes HttpCompletionOption.ResponseHeadersRead so the body is streamed to disk with a fixed-size buffer, making peak memory independent of archive size. It also passes the existing download cancellation token to GetAsync, so the download timeout covers the connect/headers phase as well.

We've been running this change built from source on our Kubernetes-hosted runners and it resolves the OOM.

🤖 Generated with Claude Code

DownloadRepositoryArchive called HttpClient.GetAsync with the default
HttpCompletionOption.ResponseContentRead, which buffers the entire
archive in the managed heap before ReadAsStreamAsync returns, so the
subsequent CopyToAsync to the FileStream never actually streamed from
the network. For actions hosted in large repositories the tarball can
be hundreds of MB, raising peak worker memory by the full archive size
per download (and per retry) and throwing OutOfMemoryException on
memory-constrained self-hosted runners.

Pass HttpCompletionOption.ResponseHeadersRead (and the download
cancellation token) so the body is copied to disk with a fixed-size
buffer.
@benoit-nexthop
benoit-nexthop requested a review from a team as a code owner July 29, 2026 17:22
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.

Action archive downloads buffer the entire tarball in memory before writing to disk

1 participant