From 34fdbfe76dd92edd5829dd72db2728971a113cf0 Mon Sep 17 00:00:00 2001 From: Benoit Sigoure Date: Wed, 29 Jul 2026 16:46:05 +0000 Subject: [PATCH] Stream action archive downloads to disk instead of buffering in memory 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. --- src/Runner.Worker/ActionManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs index 7615309ce1c..1e26b2b58b9 100644 --- a/src/Runner.Worker/ActionManager.cs +++ b/src/Runner.Worker/ActionManager.cs @@ -1667,7 +1667,11 @@ private async Task DownloadRepositoryArchive(IExecutionContext executionContext, httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(executionContext, downloadUrl, downloadAuthToken); httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents); - using (var response = await httpClient.GetAsync(downloadUrl)) + // ResponseHeadersRead keeps the archive from being buffered entirely + // in memory before it is copied to disk; the default (ResponseContentRead) + // holds the whole tarball in the managed heap, which can OOM + // memory-constrained runners on large action repositories. + using (var response = await httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead, actionDownloadCancellation.Token)) { requestId = UrlUtil.GetGitHubRequestId(response.Headers); if (!string.IsNullOrEmpty(requestId))