From c86197e60798801f62986e4e59460b1272d0c687 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Mon, 17 Aug 2026 09:46:23 -0700 Subject: [PATCH] fix: make the self-repo git guard Windows-only The live-checkout git mutation guard blocked history-rewriting git ops (checkout, reset --hard, rebase, cherry-pick, ...) in the running source checkout and its worktrees on every platform. The hazard it protects against is only real on Windows, where NTFS locks loaded module files and an in-place rewrite can corrupt the running process. On POSIX, open file handles pin the old inodes, so a checkout swap under a running process is safe, and the guard mostly taxed normal dev/salvage workflows with clone workarounds. - tools/self_repo_guard.py: add guard_active() -> os.name == "nt" - tools/terminal_tool.py: consult guard_active() before running the detector; detector logic and block message unchanged for Windows - tests: wiring tests force the guard on; new tests cover the POSIX pass-through and the platform predicate --- tests/tools/test_terminal_self_repo_guard.py | 20 +++++++++++++++++++- tools/self_repo_guard.py | 13 +++++++++++++ tools/terminal_tool.py | 17 ++++++++++++----- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/tests/tools/test_terminal_self_repo_guard.py b/tests/tools/test_terminal_self_repo_guard.py index 0596fa74fe..ff3fb0b213 100644 --- a/tests/tools/test_terminal_self_repo_guard.py +++ b/tests/tools/test_terminal_self_repo_guard.py @@ -32,10 +32,12 @@ def repo(tmp_path): return root.resolve() -def _run(command, config, monkeypatch, repo_root, session_cwds=None, **kwargs): +def _run(command, config, monkeypatch, repo_root, session_cwds=None, + guard_on=True, **kwargs): from tools.terminal_tool import terminal_tool monkeypatch.setattr(self_repo_guard, "get_running_source_root", lambda: repo_root) + monkeypatch.setattr(self_repo_guard, "guard_active", lambda: guard_on) mock_env = MagicMock() mock_env.execute.return_value = {"output": "ok", "returncode": 0} mock_env.cwd = config["cwd"] @@ -116,3 +118,19 @@ class TestSelfRepoGuardWiring: result, env = _run("git checkout main", config, monkeypatch, None) assert result.get("status") != "blocked" env.execute.assert_called_once() + + def test_guard_inactive_passes_through(self, repo, monkeypatch): + """POSIX (guard_active() False): mutations in the source repo run.""" + config = _make_env_config(cwd=str(repo)) + result, env = _run( + "git reset --hard origin/main", config, monkeypatch, repo, + guard_on=False, + ) + assert result.get("status") != "blocked" + env.execute.assert_called_once() + + def test_guard_active_matches_platform(self): + """guard_active() is True exactly on Windows.""" + import os + + assert self_repo_guard.guard_active() == (os.name == "nt") diff --git a/tools/self_repo_guard.py b/tools/self_repo_guard.py index aa92df2811..53d3a9675c 100644 --- a/tools/self_repo_guard.py +++ b/tools/self_repo_guard.py @@ -695,6 +695,19 @@ def _find_mutation(command: str, cwd: Path, root: Path, depth: int = 0) -> str | return None +def guard_active() -> bool: + """Whether the self-repo git guard applies on this platform. + + Windows-only: NTFS locks loaded .py/.pyd files and an in-place overwrite + of the live checkout can corrupt the running process. On POSIX, open file + handles keep the old inode alive, so a checkout swap under a running + process is safe — already-imported modules keep executing the old code + and the mixed-module hazard is limited to later lazy imports, which is + not worth blocking every git workflow for. + """ + return os.name == "nt" + + def detect_self_repo_git_mutation( command: str, cwd: str | None, diff --git a/tools/terminal_tool.py b/tools/terminal_tool.py index 257e85534d..0d90fe5592 100644 --- a/tools/terminal_tool.py +++ b/tools/terminal_tool.py @@ -2967,18 +2967,25 @@ def terminal_tool( "status": "blocked" }, ensure_ascii=False) - # Non-bypassable: rewriting the local checkout backing this interpreter - # can mix module versions. Remote backends cannot reach that checkout. + # Windows-only: NTFS locks loaded module files, so rewriting the local + # checkout backing this interpreter can corrupt the running process. + # POSIX keeps old inodes alive for open handles, so the guard is off + # there. Remote backends cannot reach that checkout. if env_type == "local": - from tools.self_repo_guard import detect_self_repo_git_mutation + from tools.self_repo_guard import ( + detect_self_repo_git_mutation, + guard_active, + ) guard_cwd = _resolve_command_cwd( workdir=workdir, default_cwd=cwd, session_key=session_key, ) - _self_repo_hit, _self_repo_msg = detect_self_repo_git_mutation( - command, guard_cwd + _self_repo_hit, _self_repo_msg = ( + detect_self_repo_git_mutation(command, guard_cwd) + if guard_active() + else (False, None) ) if _self_repo_hit: logger.warning(