Skip to content

Fix: test race condition corrupts repos.json with test data#222

Merged
PureWeen merged 2 commits intomainfrom
fix/repos-vanished
Feb 26, 2026
Merged

Fix: test race condition corrupts repos.json with test data#222
PureWeen merged 2 commits intomainfrom
fix/repos-vanished

Conversation

@PureWeen
Copy link
Copy Markdown
Owner

Problem

Running dotnet test could corrupt ~/.polypilot/repos.json - replacing all real repositories with test data (repo-1, MyRepo). This caused all repositories and worktrees to vanish from the UI.

Root Cause

RepoManager uses static fields for path resolution with lazy ??= initialization. The test setup calls SetBaseDirForTesting() to redirect writes to a temp directory, but ??= is not atomic and the tests could race on the static fields, causing Save() to write to the real path.

Fix

  • Lock all static path resolution with _pathLock
  • RepoManagerTests uses SetBaseDirForTesting() instead of raw field reflection
  • Added [Collection("BaseDir")] to RepoManagerTests
  • Added 2 guard tests verifying RepoManager paths point to test directory

Tests

1400/1400 passing

PureWeen and others added 2 commits February 25, 2026 21:24
…t race

Root cause: RepoManager used non-atomic ??= for static path caching (StateFile,
ReposDir, WorktreesDir). Tests that call SetBaseDirForTesting used Volatile.Write
to clear the cache, but parallel test execution could interleave between the
override write and the cache resolution, causing Save() to write to the real
~/.polypilot/repos.json instead of the test temp directory.

This corrupted the user's repos.json with test data ('repo-1', 'MyRepo') and
wiped all real repository registrations.

Fix:
- Add _pathLock around all static path resolution (getters and setter)
- RepoManagerTests uses SetBaseDirForTesting instead of raw field reflection
- Add [Collection('BaseDir')] to RepoManagerTests for serialization
- Add guard tests verifying RepoManager paths point to test directory

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…le.Read cleanup

Finding 1: CopilotService had the same static-path race condition as RepoManager.
Added _pathLock around all static path getters and SetBaseDirForTesting.

Finding 2: Save() and Load() in RepoManager accessed StateFile twice without
holding the lock across both calls. Captured to local variable once.

Finding 3: GetBaseDir() used Volatile.Read paired with a lock-protected write.
Replaced with plain read since it's only called within _pathLock.

All 1400 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen PureWeen merged commit c6fdd7b into main Feb 26, 2026
@PureWeen PureWeen deleted the fix/repos-vanished branch February 26, 2026 04:19
PureWeen added a commit that referenced this pull request Feb 27, 2026
PR #222 fixed the test race that corrupts repos.json, but the damage
was already done — real repos were replaced with test data (repo-1).
The bare clones still exist on disk but aren't tracked in repos.json.

Load() now calls HealMissingRepos() which scans the repos/ directory
for bare clones that exist on disk but aren't in the state file,
reads their remote URL from git config, and re-adds them. Also
reconstructs missing worktree entries from the worktrees/ directory.

Added 4 tests for the self-healing logic.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant