Copy the push guard next to the hook it serves

b68217655804 · AtlantisPleb · · parent 39b5f9ace52c

Copy the push guard next to the hook it serves

The installed hook ran the guard out of the worktree, which fails the moment a
worktree sits on a branch older than the guard: the hook execs a path that
does not exist, and every push from that checkout dies on `No such file` --
including pushes to the forge, which is the one destination it means to allow.
This machine reproduced it immediately, since the main checkout is parked on a
branch that predates the guard.

The installer now copies the guard into the hooks directory and the hook runs
that copy, so the guard is independent of whatever any worktree has checked
out. Re-run the installer to pick up a newer guard.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016o8HwTaqLKEWCHTjsjFtrB
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified ops/dev/install-push-guard.sh
  • modified test/openagents/push_remote_contract_test.exs

Diff

2 files changed, +17 -2

ops/dev/install-push-guard.sh modified +12 -2

@@ -13,6 +13,12 @@ set -eu

13 13
# this clone. Run it once per clone, on every machine.
14 14
#
15 15
#   sh ops/dev/install-push-guard.sh [--force]
16
#
17
# The guard is copied next to the hook rather than run from the worktree. A
18
# worktree can sit on a branch older than the guard, or on one that never had
19
# it, and a hook that execs a missing file refuses every push -- including the
20
# ones to the forge -- with a confusing error. Re-run the installer to pick up
21
# a newer guard.
16 22
17 23
force=${1:-}
18 24

@@ -36,13 +42,17 @@ fi

36 42
37 43
mkdir -p "$common_dir/hooks"
38 44
45
guard_path="$common_dir/hooks/openagents-push-remote-check.sh"
46
cp "$repo_root/ops/ci/push-remote-check.sh" "$guard_path"
47
chmod +x "$guard_path"
48
39 49
cat >"$hook_path" <<'HOOK'
40 50
#!/bin/sh
41 51
# openagents-push-guard — installed by ops/dev/install-push-guard.sh
42 52
set -eu
43 53
44
repo_root=$(git rev-parse --show-toplevel)
45
exec "$repo_root/ops/ci/push-remote-check.sh" "$@"
54
common_dir=$(cd "$(git rev-parse --git-common-dir)" && pwd)
55
exec "$common_dir/hooks/openagents-push-remote-check.sh" "$@"
46 56
HOOK
47 57
48 58
chmod +x "$hook_path"
test/openagents/push_remote_contract_test.exs modified +5

@@ -60,6 +60,11 @@ defmodule OpenAgents.PushRemoteContractTest do

60 60
    hook = Path.join(root, ".git/hooks/pre-push")
61 61
    assert File.exists?(hook)
62 62
63
    # The guard travels with the hook, so a worktree on a branch that predates
64
    # it still refuses the wrong remote instead of failing to find a file.
65
    assert File.exists?(Path.join(root, ".git/hooks/openagents-push-remote-check.sh"))
66
    File.rm!(Path.join(root, @script))
67
63 68
    assert {output, 1} =
64 69
             System.cmd("sh", [hook, "origin", "git@github.com:OpenAgentsInc/x.git"],
65 70
               cd: root,

This page updates live while a promote is in flight · changelog