-
Notifications
You must be signed in to change notification settings - Fork 0
Fix RepositoryPaths.BuildVariables temp file deletion issue #151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: BenjaminMichaelis <22186029+BenjaminMichaelis@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors BuildVariables to defer loading until first access and adds error handling for missing temp files or directories, preventing the static initializer crash. It also introduces a unit test to confirm graceful degradation when the temp file is deleted.
- Switch
BuildVariablesfrom eager static initialization to a lazily-loaded getter withLoadBuildVariables(). - Catch
FileNotFoundExceptionandDirectoryNotFoundException, returning an empty dictionary. - Add
BuildVariables_HandlesFileNotFound_Gracefullytest to verify behavior when the temp file is absent.
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| IntelliTect.Multitool/RepositoryPaths.cs | Replaced eager initialization with lazy loading and error handling. |
| IntelliTect.Multitool.Tests/RepositoryPaths.Tests.cs | Added test for missing temp file scenario and reset cache via reflection. |
Comments suppressed due to low confidence (1)
IntelliTect.Multitool.Tests/RepositoryPaths.Tests.cs:47
- [nitpick] Consider adding a test for the DirectoryNotFoundException case to cover the branch when the temp directory itself is missing.
[Fact]
| public static ReadOnlyDictionary<string, string?> BuildVariables | ||
| { | ||
| get | ||
| { | ||
| if (_buildVariables == null) | ||
| { | ||
| _buildVariables = LoadBuildVariables(); | ||
| } | ||
| return _buildVariables; | ||
| } | ||
| } | ||
|
|
||
| private static ReadOnlyDictionary<string, string?>? _buildVariables; |
Copilot
AI
Jun 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lazy initialization of _buildVariables isn’t thread-safe. Consider using Lazy or a lock to prevent race conditions if BuildVariables is accessed concurrently.
| public static ReadOnlyDictionary<string, string?> BuildVariables | |
| { | |
| get | |
| { | |
| if (_buildVariables == null) | |
| { | |
| _buildVariables = LoadBuildVariables(); | |
| } | |
| return _buildVariables; | |
| } | |
| } | |
| private static ReadOnlyDictionary<string, string?>? _buildVariables; | |
| public static ReadOnlyDictionary<string, string?> BuildVariables => _buildVariables.Value; | |
| private static readonly Lazy<ReadOnlyDictionary<string, string?>> _buildVariables = | |
| new Lazy<ReadOnlyDictionary<string, string?>>(LoadBuildVariables); |
| string filePath = Path.Combine(Path.GetTempPath(), BuildVariableFileName); | ||
| var lines = File.ReadAllLines(filePath); | ||
| var dictionary = lines | ||
| .Select(line => line.Split("::")) |
Copilot
AI
Jun 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Split("::") splits on individual ':' characters rather than the literal "::" token. If the intent is to split on "::", use Split(new[] { "::" }, StringSplitOptions.None) for correct behavior.
| .Select(line => line.Split("::")) | |
| .Select(line => line.Split(new[] { "::" }, StringSplitOptions.None)) |
| return new ReadOnlyDictionary<string, string?>(new Dictionary<string, string?>()); | ||
| } | ||
| catch (DirectoryNotFoundException) | ||
| { | ||
| // Return empty dictionary if the temp directory doesn't exist | ||
| return new ReadOnlyDictionary<string, string?>(new Dictionary<string, string?>()); |
Copilot
AI
Jun 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Allocating a new empty dictionary on each catch can be wasteful. Consider defining a static readonly empty ReadOnlyDictionary to reuse and avoid repeated allocations.
| return new ReadOnlyDictionary<string, string?>(new Dictionary<string, string?>()); | |
| } | |
| catch (DirectoryNotFoundException) | |
| { | |
| // Return empty dictionary if the temp directory doesn't exist | |
| return new ReadOnlyDictionary<string, string?>(new Dictionary<string, string?>()); | |
| return _emptyReadOnlyDictionary; | |
| } | |
| catch (DirectoryNotFoundException) | |
| { | |
| // Return empty dictionary if the temp directory doesn't exist | |
| return _emptyReadOnlyDictionary; |
| try | ||
| { | ||
| // Reset the static field to force re-initialization | ||
| var field = typeof(RepositoryPaths).GetField("_buildVariables", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); |
Copilot
AI
Jun 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Using reflection to reset a private static field makes the test brittle. Consider adding an internal reset method or using a public test hook to clear the cache more robustly.
The
RepositoryPaths.BuildVariablesproperty was failing with aTypeInitializationExceptionwhen the temporary build variables file gets deleted by Windows cleanup processes, making the entireRepositoryPathsclass unusable.Problem
This occurred when:
Root Cause
The
BuildVariablesproperty was initialized eagerly as a static readonly property that immediately tried to read from a temp file without any error handling:Solution
Changed
BuildVariablesto use lazy initialization with proper error handling:FileNotFoundExceptionandDirectoryNotFoundExceptionTryGetValue()which handles missing keys appropriatelyChanges Made
BuildVariables_HandlesFileNotFound_Gracefullyto verify the fixVerification
TypeInitializationExceptionwhen temp file is missingBuildVariablesreturns empty dictionary gracefully when file doesn't existGetDefaultRepoRoot()handles missing BuildVariables appropriatelyThe fix is minimal and surgical, maintaining full backward compatibility while resolving the crash scenario.
Fixes #137.
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.