From 4a38dfada0df53d03a74fb477b567a0528f86eb5 Mon Sep 17 00:00:00 2001 From: hotragn Date: Tue, 18 Aug 2026 16:11:33 -0400 Subject: [PATCH 1/2] fix(tests): isolate the user home on Windows so pytest cannot write the real registry `isolated_home` patched only $HOME. `core.paths.home_dir()` resolves the user home through `Path.home()`, and on Windows `ntpath.expanduser` reads %USERPROFILE% and ignores $HOME. Every test using the fixture therefore ran against the developer's real `~/.codealmanac` on Windows. `uv run pytest` on a clean Windows checkout created a real `~/.codealmanac/codealmanac.db` holding a repositories row pointing at a pytest temp directory, plus `~/.codealmanac/repos//index.db`. Because registry entries are never auto-dropped, that row survives the run and every later one. The fixture now patches %USERPROFILE% as well, and fails loudly if `Path.home()` does not land inside the sandbox, so an insufficient patch set can never silently degrade into using the real home again. This also fixes two pre-existing Windows failures that were masked by the escape: `test_default_user_state_paths_are_product_specific` and `test_cli_setup_and_uninstall_codex_instructions`. --- tests/conftest.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index b3fe78a9..141e0e40 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -81,7 +81,18 @@ def bind_inline_executor( def isolated_home(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]: home = tmp_path / "home" home.mkdir() + # `home_dir()` resolves `~` through `Path.home()`. POSIX reads $HOME, but + # Windows reads %USERPROFILE% and ignores $HOME entirely, so patching only + # $HOME leaves user state pointing at the real `~/.codealmanac`. monkeypatch.setenv("HOME", str(home)) + monkeypatch.setenv("USERPROFILE", str(home)) + resolved = Path.home() + if resolved != home: + raise RuntimeError( + "isolated_home failed to redirect the user home: " + f"Path.home() is {resolved}, expected {home}. " + "Tests must never read or write the real ~/.codealmanac." + ) yield home From 502821189a6547874f5e8470ba5a2432b36bb189 Mon Sep 17 00:00:00 2001 From: hotragn Date: Tue, 18 Aug 2026 16:35:20 -0400 Subject: [PATCH 2/2] fix(tests): sandbox the user home for every test, not only tests that ask for it 45 tests build an app or invoke the CLI without requesting `isolated_home` and without passing an explicit `database_path`, so `AppConfig()` falls back to `~/.codealmanac` and they operate on real global user state. This is not Windows-specific: `Path.home()` is the developer's real home on every platform. The visible consequence is that `uv run pytest` is not repeatable. `repositories.name` is `NOT NULL UNIQUE`, and `RepositoryStore.remember` upserts `ON CONFLICT(repository_id)` only, so a second run registers a fresh temp path under the already-taken name "repo" and raises: tests/test_validate.py::test_cli_validate_returns_nonzero_for_issues IntegrityError: UNIQUE constraint failed: repositories.name Reproduced on main, from a clean `~/.codealmanac`: uv run pytest tests/test_validate.py -q # 7 passed uv run pytest tests/test_validate.py -q # 1 failed, 6 passed CI does not see it because each job starts from a fresh container, so the second run never happens. Rather than add the fixture to 45 call sites, `isolated_home` becomes `autouse`. Every test now gets a sandboxed home under its own `tmp_path`, the 390 existing explicit requests keep resolving to the same object and are unchanged, and no future test can opt out of the invariant by forgetting an argument. This mirrors the existing autouse `disable_external_telemetry_during_tests` fixture. Full suite is unchanged at 11 failed / 552 passed, and the same two runs above now pass twice with `~/.codealmanac` never created. Stacked on the %USERPROFILE% fix, which this needs in order to sandbox anything at all on Windows. --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 141e0e40..060eafb6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -77,7 +77,7 @@ def bind_inline_executor( spawner.bind(app.workflows.queue.executor) -@pytest.fixture +@pytest.fixture(autouse=True) def isolated_home(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]: home = tmp_path / "home" home.mkdir()