From 01b90e38e24bbea3fd325135bfc87c567800dc09 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Wed, 29 Jul 2026 16:43:19 -0500 Subject: [PATCH] fix(coord): the shim ran the CALLER's checkout, so stale worktrees got no coordination Shipped in #52 and wrong. The hook shim resolved `git rev-parse --show-toplevel`, i.e. the worktree the session is sitting in, and ran that checkout's script. A worktree on a branch that predates a coordination change has none of those scripts -- so the shim found nothing and exited 0. That session got no banner and no collision gate, and NOTHING REPORTED THE ABSENCE. Silence is indistinguishable from "no collisions", which is the same shape as the bug this whole layer exists to fix. Measured immediately after installing: the `sweep` worktree sits on a branch older than the merge and has no overlap.ps1 or collision_gate.ps1 at all, so it ran the previous protocol for the banner and no gate whatsoever. Coordination is INFRASTRUCTURE and has to be uniform. Two sessions running different versions of the collision protocol is exactly the drift the shared liveness fence was consolidated to prevent -- and it is worse here, because the divergence is invisible from both sides. The shim now resolves the PRIMARY checkout (via --git-common-dir), which tracks main, so every session runs the same current code whatever branch it is on. The calling worktree stays as a fallback for layouts where the primary is absent. The new test builds a primary holding the script plus a worktree deliberately WITHOUT it, and asserts the shim reaches the primary's copy. Verified by mutation: reverting the shim to cwd-resolution reds it, so it pins the behaviour rather than the script merely running. --- scripts/coord/install-coordination.ps1 | 39 ++++++++++----- tests/test_collision_gate.py | 67 ++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 11 deletions(-) diff --git a/scripts/coord/install-coordination.ps1 b/scripts/coord/install-coordination.ps1 index 79a92599..28fb5eac 100644 --- a/scripts/coord/install-coordination.ps1 +++ b/scripts/coord/install-coordination.ps1 @@ -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) @@ -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 } } }' ) } diff --git a/tests/test_collision_gate.py b/tests/test_collision_gate.py index 319b6f12..1cc8a79f 100644 --- a/tests/test_collision_gate.py +++ b/tests/test_collision_gate.py @@ -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."""