fix(kvb-org-organize): count only real git markers in the council gate
An empty /tmp/.git (2026-11-11 19:47, owner unknown) made council.py gate treat all of /tmp as a git worktree: every unclaimed Edit or Write under /tmp was blocked and test_council turned every skills gate red. A parent now counts only when its .git is a directory holding HEAD or a regular file starting with gitdir:, as git itself requires, so normal repos, linked worktrees and submodules still block unclaimed edits. A FIFO named .git is never opened, so it cannot hang the hook. Root fix proposed by the chair Main. 🎯 Fix: 94 % 💥 Break: 3 % 💸 Cost: 100 % ⏱️ Time: 100 % 🛡️ Security: 100 % 🏆 Score: 91 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4ab13b6e65
commit
9f598a56ab
2 files changed
+38
-6
No files matched your search
@@ -11,7 +11,9 @@ Environment:
|
||||
Exit codes:
|
||||
0 done, 2 usage error, 3 office held by a live session,
|
||||
4 office held by a dead session (take it over with `takeover`),
|
||||
gate: 2 blocks the tool call.
|
||||
gate: 2 blocks the tool call: an edit without a claim of this session
|
||||
below a directory whose .git is a directory holding HEAD or a file
|
||||
starting with gitdir:.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -284,8 +286,21 @@ def cmd_list(args: argparse.Namespace) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def is_git_marker(marker: Path) -> bool:
|
||||
if marker.is_dir():
|
||||
return (marker / "HEAD").is_file()
|
||||
# is_file() first: opening a planted FIFO named .git would hang this hook.
|
||||
if not marker.is_file():
|
||||
return False
|
||||
try:
|
||||
with marker.open("rb") as handle:
|
||||
return handle.read(7) == b"gitdir:"
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def in_git_worktree(target: Path) -> bool:
|
||||
return any((path / ".git").exists() for path in (target, *target.parents))
|
||||
return any(is_git_marker(path / ".git") for path in (target, *target.parents))
|
||||
|
||||
|
||||
def cmd_gate(_: argparse.Namespace) -> int:
|
||||
|
||||
+21
-4
@@ -48,8 +48,15 @@ class TestCouncil(unittest.TestCase):
|
||||
input=stdin,
|
||||
cwd=cwd,
|
||||
check=False,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
def _repo(self) -> Path:
|
||||
repo = Path(self.tmp.name) / "repo"
|
||||
(repo / ".git").mkdir(parents=True)
|
||||
(repo / ".git" / "HEAD").write_text("ref: refs/heads/main\n", encoding="utf-8")
|
||||
return repo
|
||||
|
||||
def test_first_claim_wins_and_second_is_refused(self):
|
||||
self.assertEqual(self._run("s1", "claim", "work/x", "--work", "w").returncode, 0)
|
||||
second = self._run("s2", "claim", "work/x", "--work", "w")
|
||||
@@ -131,8 +138,7 @@ class TestCouncil(unittest.TestCase):
|
||||
self.assertEqual(sorted(self.root.rglob("*")), [])
|
||||
|
||||
def test_gate_blocks_unclaimed_edits_in_a_git_worktree_only(self):
|
||||
repo = Path(self.tmp.name) / "repo"
|
||||
(repo / ".git").mkdir(parents=True)
|
||||
repo = self._repo()
|
||||
inside = json.dumps({"session_id": "s1", "tool_input": {"file_path": str(repo / "f")}})
|
||||
outside = json.dumps(
|
||||
{"session_id": "s1", "tool_input": {"file_path": str(self.home / "f")}}
|
||||
@@ -142,9 +148,20 @@ class TestCouncil(unittest.TestCase):
|
||||
self._run("s1", "claim", "work/x", "--work", "w")
|
||||
self.assertEqual(self._run("s1", "gate", stdin=inside).returncode, 0)
|
||||
|
||||
def test_gate_counts_only_a_git_dir_holding_head_or_a_gitdir_file(self):
|
||||
base = Path(self.tmp.name)
|
||||
(base / "stray" / ".git").mkdir(parents=True)
|
||||
(base / "linked").mkdir()
|
||||
(base / "linked" / ".git").write_text("gitdir: /elsewhere/.git/worktrees/linked\n", encoding="utf-8")
|
||||
(base / "fifo").mkdir()
|
||||
os.mkfifo(base / "fifo" / ".git", 0o600)
|
||||
for folder, code in (("stray", 0), ("linked", 2), ("fifo", 0)):
|
||||
with self.subTest(folder=folder):
|
||||
edit = json.dumps({"session_id": "s1", "tool_input": {"file_path": str(base / folder / "f")}})
|
||||
self.assertEqual(self._run("s1", "gate", stdin=edit).returncode, code)
|
||||
|
||||
def test_gate_still_blocks_past_a_planted_fifo_or_malformed_claim(self):
|
||||
repo = Path(self.tmp.name) / "repo"
|
||||
(repo / ".git").mkdir(parents=True)
|
||||
repo = self._repo()
|
||||
inside = json.dumps({"session_id": "s1", "tool_input": {"file_path": str(repo / "f")}})
|
||||
(self.root / "work" / "fifo").mkdir(parents=True)
|
||||
os.mkfifo(self.root / "work" / "fifo" / "claim.json", 0o600)
|
||||
|
||||
Reference in new issue
Block a user