actions / actions/runner

TempFirectoryManager fails to clean temp directory due to error in info gathering phase.

Open
#4,251 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
C#
Stars
6.3k
Forks
1.4k
Avg merge
1d 16h
Merged PRs (30d)
24

Description

Describe the bug

TempDirectoryManager fails to complete its information gathering phase when it encounters a file with permissions that do not allow it access, and the cleanup does not take place. This causes the temp directory to constantly grow, and subsequently cleanup jobs get longer and longer, and the disk to fill up.

We do not know how the directory (.ssh) came to have permissions that do not allow the service user (ghrunner) access to it.

Relevant log section;

[2026-02-16 17:25:30Z INFO TempDirectoryManager] Cleaning runner temp folder: /home/ghrunner/actions-runner/_work/_temp
[2026-02-16 17:31:43Z ERR  TempDirectoryManager] System.AggregateException: One or more errors occurred. (Access to the path '/home/ghrunner/actions-runner/_work/_temp/_github_home/.ssh' is denied.)
 ---> System.UnauthorizedAccessException: Access to the path '/home/ghrunner/actions-runner/_work/_temp/_github_home/.ssh' is denied.
 ---> System.IO.IOException: Permission denied
   --- End of inner exception stack trace ---
   at System.IO.Enumeration.FileSystemEnumerator`1.Init()
   at System.IO.DirectoryInfo.InternalEnumerateInfos(String path, String searchPattern, SearchTarget searchTarget, EnumerationOptions options)
   at GitHub.Runner.Sdk.IOUtil.Enumerate(DirectoryInfo directory, CancellationTokenSource tokenSource)+MoveNext()
   at System.Linq.Parallel.PartitionedDataSource`1.ContiguousChunkLazyEnumerator.MoveNext(T& currentElement, Int32& currentKey)
   at System.Linq.Parallel.SpoolingTaskBase.Work()
   at System.Linq.Parallel.QueryTask.BaseWork(Object unused)
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
   --- End of inner exception stack trace ---
   at System.Linq.Parallel.QueryTaskGroupState.QueryEnd(Boolean userInitiatedDispose)
   at System.Linq.Parallel.SpoolingTask.SpoolForAll[TInputOutput,TIgnoreKey](QueryTaskGroupState groupState, PartitionedStream`2 partitions, TaskScheduler taskScheduler)
   at System.Linq.Parallel.DefaultMergeHelper`2.System.Linq.Parallel.IMergeHelper<TInputOutput>.Execute()
   at System.Linq.Parallel.MergeExecutor`1.Execute()
   at System.Linq.Parallel.MergeExecutor`1.Execute[TKey](PartitionedStream`2 partitions, Boolean ignoreOutput, ParallelMergeOptions options, TaskScheduler taskScheduler, Boolean isOrdered, CancellationState cancellationState, Int32 queryId)
   at System.Linq.Parallel.PartitionedStreamMerger`1.Receive[TKey](PartitionedStream`2 partitionedStream)
   at System.Linq.Parallel.ForAllOperator`1.WrapPartitionedStream[TKey](PartitionedStream`2 inputStream, IPartitionedStreamRecipient`1 recipient, Boolean preferStriping, QuerySettings settings)
   at System.Linq.Parallel.UnaryQueryOperator`2.UnaryQueryOperatorResults.ChildResultsRecipient.Receive[TKey](PartitionedStream`2 inputStream)
   at System.Linq.Parallel.ScanQueryOperator`1.ScanEnumerableQueryOperatorResults.GivePartitionedStream(IPartitionedStreamRecipient`1 recipient)
   at System.Linq.Parallel.UnaryQueryOperator`2.UnaryQueryOperatorResults.GivePartitionedStream(IPartitionedStreamRecipient`1 recipient)
   at System.Linq.Parallel.QueryOperator`1.GetOpenedEnumerator(Nullable`1 mergeOptions, Boolean suppressOrder, Boolean forEffect, QuerySettings querySettings)
   at System.Linq.Parallel.ForAllOperator`1.RunSynchronously()
   at GitHub.Runner.Sdk.IOUtil.DeleteDirectory(String path, Boolean contentsOnly, Boolean continueOnContentDeleteError, CancellationToken cancellationToken)
   at GitHub.Runner.Worker.TempDirectoryManager.CleanupTempDirectory()
[2026-02-16 17:31:43Z ERR  TempDirectoryManager] #####################################################
[2026-02-16 17:31:43Z ERR  TempDirectoryManager] System.UnauthorizedAccessException: Access to the path '/home/ghrunner/actions-runner/_work/_temp/_github_home/.ssh' is denied.
 ---> System.IO.IOException: Permission denied
   --- End of inner exception stack trace ---
   at System.IO.Enumeration.FileSystemEnumerator`1.Init()
   at System.IO.DirectoryInfo.InternalEnumerateInfos(String path, String searchPattern, SearchTarget searchTarget, EnumerationOptions options)
   at GitHub.Runner.Sdk.IOUtil.Enumerate(DirectoryInfo directory, CancellationTokenSource tokenSource)+MoveNext()
   at System.Linq.Parallel.PartitionedDataSource`1.ContiguousChunkLazyEnumerator.MoveNext(T& currentElement, Int32& currentKey)
   at System.Linq.Parallel.SpoolingTaskBase.Work()
   at System.Linq.Parallel.QueryTask.BaseWork(Object unused)
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
[2026-02-16 17:31:43Z ERR  TempDirectoryManager] #####################################################
[2026-02-16 17:31:43Z ERR  TempDirectoryManager] System.IO.IOException: Permission denied
[2026-02-16 17:31:43Z INFO HostContext] Well known directory 'Bin': '/home/ghrunner/actions-runner/bin.2.331.0'
[2026-02-16 17:31:43Z INFO HostContext] Well known directory 'Root': '/home/ghrunner/actions-runner'
[2026-02-16 17:31:43Z INFO HostContext] Well known directory 'Diag': '/home/ghrunner/actions-runner/_diag'
[2026-02-16 17:31:43Z INFO HostContext] Well known config file 'Telemetry': '/home/ghrunner/actions-runner/_diag/.telemetry'
[2026-02-16 17:31:43Z INFO JobRunner] Raising job completed against run service
[2026-02-16 17:31:43Z INFO Worker] Job completed.

To Reproduce
Steps to reproduce the behavior:

  1. Run some jobs which create a file which can not be accessed by the service user.
  2. Run more jobs
  3. See the temp directory manager fail, and the disk fill up.

Expected behavior
Temp DirectoryManager should skip files it can not clean up, and clean up those which it has access to.

Runner Version and Platform

Version of your runner?

Ubuntu 24.04.4 running 2.331

What's not working?

action runners' temp directory is constantly growing

Job Log Output

Job log output does not indicate any issues, but jobs wait for longer and longer on the completed phase.

Runner and Worker's Diagnostic Logs

Error from logs shown at top of report, otherwise log is normal for a job.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with TempDirectoryManager.CleanupTempDirectory and follow its call to IOUtil.DeleteDirectory and IOUtil.Enumerate. Reproduce the permission-denied case described for the .ssh directory, then verify that inaccessible files are skipped while accessible contents are cleaned up and the cleanup does not abort.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
devops
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.