Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 28 additions & 11 deletions scripts/coord/install-coordination.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@
THE FIX: wire the hooks at USER level (~/.claude/settings.json), which is per-machine and loads in
every worktree regardless of how it was created -- the same place the worktree gate already lives.

NO INSTALLED COPY. Each hook is a one-line shim that resolves the repo from the session's cwd and
runs THAT checkout's script. So there is nothing to go stale: after a `git pull` the hook is
current, everywhere, immediately. This is deliberate -- the worktree gate's installer copies its
script, and running it from a stale checkout has already silently downgraded the live gate once.
NO INSTALLED COPY. Each hook is a one-line shim that locates the script in a checkout and runs it,
so there is nothing to go stale: after a `git pull` the hook is current everywhere, immediately.
This is deliberate -- the worktree gate's installer copies its script, and running it from a stale
checkout has already silently downgraded the live gate once.

THE SHIM RESOLVES THE PRIMARY CHECKOUT, not the calling worktree. Coordination is infrastructure
and must be uniform across sessions. Measured 2026-07-29: a worktree sitting on a branch that
predated the coordination merge had none of these scripts, so a cwd-resolved shim found nothing
and exited silently -- that session got no banner and no gate, and nothing reported the absence.
The primary tracks main, so every session runs the same current code whatever branch it is on.

WHAT GETS WIRED
SessionStart -> scripts/worktree/session-context.ps1 (who is live, what they build)
Expand All @@ -47,16 +53,27 @@ $ErrorActionPreference = "Stop"
# another tool (or another session) added to the same file.
$MARKER = "mefor-coord"

# The shim. Resolves the repo from the session's cwd, so one wiring serves every worktree and there is
# no copy to fall behind the checkout. Silent and exit-0 outside a repo: this file is user-global and
# will run in unrelated projects, where it must do nothing at all.
# The shim. No installed copy: it locates the script in a checkout and runs it, so a `git pull` updates
# the hook everywhere with nothing to fall stale. Silent and exit-0 outside a repo, because this file is
# user-global and runs in every unrelated project on the machine.
#
# IT RESOLVES THE PRIMARY CHECKOUT, NOT THE CURRENT WORKTREE, and that order matters. Coordination is
# INFRASTRUCTURE and has to be uniform: two sessions running different versions of the collision
# protocol is the drift the shared liveness fence exists to prevent. Measured 2026-07-29: a worktree
# sitting on a branch that predated the coordination merge had none of the scripts, so a cwd-resolved
# shim found nothing and exited silently -- the session got no banner and no gate, and nothing said so.
# The primary tracks main, so every session runs the same current code whatever its own branch is.
# The current worktree is kept only as a fallback, for a layout where the primary is unavailable.
function New-ShimCommand([string]$RelativeScript) {
return (
"# $MARKER`n" +
'$r = (& git rev-parse --show-toplevel 2>$null); ' +
'if ($LASTEXITCODE -eq 0 -and $r) { ' +
"`$s = Join-Path `$r '$RelativeScript'; " +
'if (Test-Path -LiteralPath $s) { & $s } }'
'$c = (& git rev-parse --path-format=absolute --git-common-dir 2>$null); ' +
'if ($LASTEXITCODE -eq 0 -and $c) { ' +
'$bases = @((Split-Path $c.Trim() -Parent), (& git rev-parse --path-format=absolute --show-toplevel 2>$null)); ' +
'foreach ($b in $bases) { ' +
'if (-not $b) { continue } ' +
"`$s = Join-Path `$b.Trim() '$RelativeScript'; " +
'if (Test-Path -LiteralPath $s) { & $s; break } } }'
)
}

Expand Down
67 changes: 67 additions & 0 deletions tests/test_collision_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,73 @@ def test_status_reports_installed_state(settings: Path) -> None:
assert "INSTALLED" in run_installer(settings, "-Status")


def shim_for(settings: Path, matcher_prefix: str, tmp_path: Path, name: str) -> Path:
cmd = next(
g["hooks"][0]["command"]
for g in load(settings)["hooks"]["PreToolUse"]
if g.get("matcher", "").startswith(matcher_prefix)
)
p = tmp_path / name
p.write_text(cmd, encoding="utf-8")
return p


def test_shim_runs_the_primary_checkouts_script_not_the_worktrees(
settings: Path, tmp_path: Path
) -> None:
"""A worktree on a branch that predates a coordination change has none of the scripts.

Measured: a cwd-resolved shim found nothing there and exited silently -- no banner, no gate, and
no indication either was missing. Coordination is infrastructure and must be uniform, so the shim
resolves the PRIMARY checkout (which tracks main) rather than whatever branch the caller is on.
"""
primary = tmp_path / "primary"
primary.mkdir()
_git_init(primary)
# Only the PRIMARY gets a gate script; the worktree deliberately does not have one.
(primary / "scripts" / "hooks").mkdir(parents=True)
(primary / "scripts" / "hooks" / "collision_gate.ps1").write_text(
"Write-Output 'PRIMARY-SCRIPT-RAN'\n", encoding="utf-8"
)
wt = tmp_path / "old-branch-wt"
subprocess.run(
["git", "-C", str(primary), "worktree", "add", "-q", "-b", "old", str(wt)],
check=True,
capture_output=True,
text=True,
)
assert not (wt / "scripts" / "hooks" / "collision_gate.ps1").exists()

run_installer(settings)
shim = shim_for(settings, "Edit", tmp_path, "shim.ps1")
proc = subprocess.run(
["pwsh", "-NoProfile", "-NonInteractive", "-File", str(shim)],
cwd=str(wt),
input=json.dumps({"tool_name": "Edit", "tool_input": {"file_path": "x.py"}}),
capture_output=True,
text=True,
timeout=180,
check=False,
)
assert "PRIMARY-SCRIPT-RAN" in proc.stdout, (
f"shim did not reach the primary checkout's script: {proc.stdout!r} {proc.stderr!r}"
)


def _git_init(repo: Path) -> None:
for args in (
["init", "-q"],
["config", "user.email", "t@example.invalid"],
["config", "user.name", "t"],
):
subprocess.run(["git", "-C", str(repo), *args], check=True, capture_output=True, text=True)
(repo / "f.txt").write_text("x", encoding="utf-8")
subprocess.run(["git", "-C", str(repo), "add", "f.txt"], check=True, capture_output=True)
subprocess.run(
["git", "-C", str(repo), "commit", "-qm", "init"], check=True, capture_output=True
)


def test_installed_shim_is_inert_outside_a_git_repo(settings: Path, tmp_path: Path) -> None:
"""User settings are global: this hook runs in every unrelated project on the machine and must
do nothing there rather than erroring on each tool call."""
Expand Down
Loading