Skip to content

UnauthorizedAccessException when deleting PortableGit directory during Git upgrade #1554

@titansonico10

Description

@titansonico10

Description

When Stability Matrix detects a Git version mismatch during InstallGitIfNecessary, it attempts to delete the existing PortableGit directory before downloading the new version. This fails with an UnauthorizedAccessException on git.exe, crashing the upgrade process.

Exception

UnauthorizedAccessException: Access to the path 'git.exe' is denied.

Sentry ID: aedf9c2c

Stack Trace

at System.IO.FileSystem.RemoveDirectoryRecursive(String fullPath, WIN32_FIND_DATA& findData, Boolean topLevel)
at System.IO.FileSystem.RemoveDirectory(String fullPath, Boolean recursive)
at StabilityMatrix.Core.Models.FileInterfaces.DirectoryPath.<>c__DisplayClass25_0.<DeleteAsync>b__0()
...
at StabilityMatrix.Avalonia.Helpers.WindowsPrerequisiteHelper.InstallGitIfNecessary(IProgress`1 progress)
at StabilityMatrix.Avalonia.ViewModels.Settings.MainSettingsViewModel.RunGitProcess()

Root Cause Analysis

The relevant code in InstallGitIfNecessary:

if (Directory.Exists(PortableGitInstallDir))
{
    await new DirectoryPath(PortableGitInstallDir).DeleteAsync(true);
}

The UnauthorizedAccessException is thrown by RemoveDirectoryRecursive when trying to delete git.exe. Two likely causes have been identified:

  1. Git process still running in background — If a git.exe process spawned by Stability Matrix is still alive (e.g. a previous git operation didn't fully exit), Windows will deny deletion of the executable because it is locked by the running process. The affected user confirmed that git.exe did not have the read-only attribute set, which points to this as the primary cause.

  2. Read-only file attributes — In some environments, files inside the PortableGit distribution may carry the read-only attribute, which also causes UnauthorizedAccessException on deletion.

Suggested Fix

Before attempting to delete the PortableGit directory, the code should:

  1. Kill any running git.exe processes that originate from the SM library directory.
  2. Strip read-only attributes recursively as a safety net.

Example:

if (Directory.Exists(PortableGitInstallDir))
{
    // Kill any git processes that may have the executable locked
    foreach (var proc in Process.GetProcessesByName("git"))
    {
        try
        {
            if (proc.MainModule?.FileName.StartsWith(PortableGitInstallDir, StringComparison.OrdinalIgnoreCase) == true)
            {
                proc.Kill(entireProcessTree: true);
                await proc.WaitForExitAsync();
            }
        }
        catch (Exception ex)
        {
            Logger.Warn(ex, "Failed to kill git process before upgrade");
        }
    }

    // Strip read-only attributes recursively before deletion
    foreach (var file in Directory.EnumerateFiles(PortableGitInstallDir, "*", SearchOption.AllDirectories))
    {
        try { File.SetAttributes(file, File.GetAttributes(file) & ~FileAttributes.ReadOnly); }
        catch { /* ignore */ }
    }

    await new DirectoryPath(PortableGitInstallDir).DeleteAsync(true);
}

Workaround

Manually delete the PortableGit folder from the Stability Matrix library directory while the application is closed, then reopen Stability Matrix to trigger a fresh Git installation.

Environment

  • Platform: Windows
  • Component: WindowsPrerequisiteHelper.InstallGitIfNecessary
  • Triggered by: Git version upgrade check in Settings → Run Git Process

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions