fix(release-control): scrub inherited git env in hook scratch-repo helpers

Pre-commit runs from a linked git worktree export an absolute GIT_DIR.
The release-control test helpers and the test-patched branch of
git_env() only removed GIT_INDEX_FILE, so their scratch-repo commands
targeted the REAL repository: 'git init' in a tempdir re-initialized it
with core.bare=true (breaking git status/commit for every checkout and
worktree) and the subsequent scratch 'git add' failed the hook.

Scrub GIT_DIR, GIT_WORK_TREE, GIT_INDEX_FILE, and GIT_COMMON_DIR in:
- git_env() of format_staged_go.py, governance_stage_guard.py, and
  subsystem_contracts.py (test-patched branch only; production hook
  behavior unchanged)
- the scratch-repo git() helpers in format_staged_go_test.py,
  governance_stage_guard_test.py, readiness_assertion_guard_test.py,
  subsystem_contracts_test.py

Matches the pattern contract_audit_test.py and status_audit_test.py
already used. Verified the full hook test battery passes with
GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE pointed at a canary repo, which
stays un-corrupted.
This commit is contained in:
rcourtman 2026-07-12 11:30:39 +01:00
parent 906d4823aa
commit a0fda6b265
7 changed files with 38 additions and 10 deletions

View file

@ -15,11 +15,13 @@ DEFAULT_REPO_ROOT = REPO_ROOT
def git_env() -> dict[str, str]:
env = os.environ.copy()
# Unit tests patch REPO_ROOT to a temporary repository. In that case, an
# inherited alternate index from the caller should not leak into the temp
# repo and point git plumbing at entries from a different repository.
# Unit tests patch REPO_ROOT to a temporary repository. In that case, the
# inherited hook environment (alternate index, or the absolute GIT_DIR a
# pre-commit run from a linked worktree exports) should not leak into the
# temp repo and point git plumbing at a different repository.
if REPO_ROOT != DEFAULT_REPO_ROOT:
env.pop("GIT_INDEX_FILE", None)
for name in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR"):
env.pop(name, None)
return env

View file

@ -11,7 +11,11 @@ from format_staged_go import format_staged_go_files
class FormatStagedGoTest(unittest.TestCase):
def git(self, repo_root: Path, *args: str) -> subprocess.CompletedProcess:
env = os.environ.copy()
env.pop("GIT_INDEX_FILE", None)
# Scrub the full hook environment: with only GIT_INDEX_FILE removed, a
# pre-commit run from a linked worktree exports an absolute GIT_DIR and
# "git init" here re-initializes the REAL repository as bare.
for name in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR"):
env.pop(name, None)
return subprocess.run(
["git", *args],
cwd=repo_root,

View file

@ -30,7 +30,12 @@ DEFAULT_REPO_ROOT = REPO_ROOT
def git_env() -> dict[str, str]:
env = os.environ.copy()
if REPO_ROOT != DEFAULT_REPO_ROOT:
env.pop("GIT_INDEX_FILE", None)
# Unit tests patch REPO_ROOT to a temporary repository. Scrub the full
# inherited hook environment: a pre-commit run from a linked worktree
# exports an absolute GIT_DIR, which would silently point git at the
# real repository instead of the temp one.
for name in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR"):
env.pop(name, None)
return env

View file

@ -15,7 +15,11 @@ from governance_stage_guard import (
class GovernanceStageGuardTest(unittest.TestCase):
def git(self, repo_root: Path, *args: str) -> subprocess.CompletedProcess:
env = os.environ.copy()
env.pop("GIT_INDEX_FILE", None)
# Scrub the full hook environment: with only GIT_INDEX_FILE removed, a
# pre-commit run from a linked worktree exports an absolute GIT_DIR and
# "git init" here re-initializes the REAL repository as bare.
for name in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR"):
env.pop(name, None)
return subprocess.run(
["git", *args],
cwd=repo_root,

View file

@ -36,7 +36,11 @@ def base_payload() -> dict:
class ReadinessAssertionGuardTest(unittest.TestCase):
def git(self, repo_root: Path, *args: str) -> subprocess.CompletedProcess:
env = os.environ.copy()
env.pop("GIT_INDEX_FILE", None)
# Scrub the full hook environment: with only GIT_INDEX_FILE removed, a
# pre-commit run from a linked worktree exports an absolute GIT_DIR and
# "git init" here re-initializes the REAL repository as bare.
for name in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR"):
env.pop(name, None)
return subprocess.run(
["git", *args],
cwd=repo_root,

View file

@ -52,7 +52,12 @@ PATH_SUFFIXES = (
def git_env() -> dict[str, str]:
env = os.environ.copy()
if REPO_ROOT != DEFAULT_REPO_ROOT:
env.pop("GIT_INDEX_FILE", None)
# Unit tests patch REPO_ROOT to a temporary repository. Scrub the full
# inherited hook environment: a pre-commit run from a linked worktree
# exports an absolute GIT_DIR, which would silently point git at the
# real repository instead of the temp one.
for name in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR"):
env.pop(name, None)
return env

View file

@ -18,7 +18,11 @@ from subsystem_contracts import (
class SubsystemContractsTest(unittest.TestCase):
def git(self, repo_root: Path, *args: str) -> subprocess.CompletedProcess:
env = os.environ.copy()
env.pop("GIT_INDEX_FILE", None)
# Scrub the full hook environment: with only GIT_INDEX_FILE removed, a
# pre-commit run from a linked worktree exports an absolute GIT_DIR and
# "git init" here re-initializes the REAL repository as bare.
for name in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR"):
env.pop(name, None)
return subprocess.run(
["git", *args],
cwd=repo_root,