Skip to content

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

@alsutton

Description

@alsutton

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions