Skip to content

fix(hooks): resolve pre-push workspace root from the main clone, not the worktree - #490

Open
ProtocolWarden wants to merge 2 commits into
mainfrom
claude/fix-pre-push-worktree-discovery
Open

fix(hooks): resolve pre-push workspace root from the main clone, not the worktree#490
ProtocolWarden wants to merge 2 commits into
mainfrom
claude/fix-pre-push-worktree-discovery

Conversation

@ProtocolWarden

Copy link
Copy Markdown
Owner

The bug

.hooks/pre-push derived the workspace root from git rev-parse --show-toplevel:

repo_root="$(git rev-parse --show-toplevel)"
workspace_root="$(cd "$repo_root/.." && pwd)"

--show-toplevel returns the worktree root. In a git worktree checkout that is <clone>/.claude/worktrees/<name>, so $repo_root/.. was the worktrees directory — not the workspace holding the sibling repos.

Two things broke as a result, both only from a worktree:

  1. Boundary-artifact discovery globbed a directory containing nothing but other worktrees, so every worktree push failed closed on missing REPOGRAPH_BOUNDARY_ARTIFACT_FILE; failing closed — before the audit ever ran.
  2. custodian-multi resolution missed every fallback: a worktree has no .venv of its own, and $workspace_root/Custodian/.venv/ pointed inside .claude/worktrees/. With no PATH entry the hook died on custodian-multi not found. That's the exact state of the WSL fleet box, where <clone>/.venv/bin/custodian-multi is the only custodian present.

The fix

--git-common-dir always resolves to the original clone's .git:

main_repo_root="$(cd "$(dirname "$(git rev-parse --git-common-dir)")" && pwd)"
workspace_root="$(cd "$main_repo_root/.." && pwd)"

dirname + cd normalizes all three forms git emits — .git at a clone's top level, ../.git from a subdirectory, absolute from a worktree — because cd interprets a relative path against cwd, which is exactly what git means by it. This avoids --path-format=absolute (git 2.31+), so the hook keeps working on older git rather than introducing a needless version floor.

Also added $main_repo_root/{.venv,.warehouse-venv}/bin/custodian-multi to the binary candidates. For a plain checkout main_repo_root == repo_root, so they're harmless duplicates of the existing entries.

The audit target stays "$repo_root" — we audit the content being pushed, which is the worktree.

What I deliberately left alone

The command -v custodian-multi PATH-first ordering. I checked whether reordering would help: it changes nothing in either environment today. Windows has no repo venv, so PATH is the only candidate; WSL has no PATH entry, so the venv candidate already wins.

Verification

Resolution probed from all three repo shapes:

invoked from main_repo_root artifact
worktree …/GitHub/OperationsCenter found
plain clone, top level (how git invokes hooks) …/GitHub/OperationsCenter found
plain clone, subdirectory …/GitHub/OperationsCenter found

workspace_root for a plain clone is byte-identical to before, so non-worktree pushes are unaffected.

End-to-end run of the real hook from a worktree with REPOGRAPH_BOUNDARY_ARTIFACT_FILE unset now prints boundary artifact: …/PrivateManifest/dist/boundary_disclosure_artifact.json and proceeds to the audit, instead of failing closed at discovery.

bash -n clean. (shellcheck isn't installed in either environment, so it wasn't run.)

custodian-multi against this branch: 0 findings, clean.

No log.md entry

.hooks/ is in the pre-commit guard's TRIVIAL exemption, so a hook-only commit doesn't require one. Skipping it also avoids a guaranteed top-of-file conflict with #487, which is open concurrently — the same conflict problem the oc-watchdog/* exemption comment in .hooks/pre-commit describes. The root cause is documented inline at the point of use instead.

🤖 Generated with Claude Code

ProtocolWarden and others added 2 commits August 3, 2026 16:39
`git rev-parse --show-toplevel` returns the WORKTREE root. In a `git
worktree` checkout that is <clone>/.claude/worktrees/<name>, so
<repo_root>/.. was the worktrees dir — not the workspace holding the
sibling repos. Two things broke as a result, both only from a worktree:

* Boundary-artifact discovery globbed a directory containing nothing but
  other worktrees, so every worktree push failed closed on
  "missing REPOGRAPH_BOUNDARY_ARTIFACT_FILE" before the audit ever ran.
* The custodian-multi fallbacks all missed: a worktree has no .venv of
  its own, and $workspace_root/Custodian/.venv/ pointed inside
  .claude/worktrees/. With no PATH entry the hook died on
  "custodian-multi not found" — the state of the WSL fleet box, where
  <clone>/.venv/bin/custodian-multi is the only custodian present.

--git-common-dir always resolves to the ORIGINAL clone's .git: relative
to cwd for a plain checkout (".git" at the top level, "../.git" from a
subdirectory), absolute from a worktree. dirname + cd normalizes all
three, since cd interprets a relative path against cwd — which is
exactly what git means by it. Avoids --path-format=absolute so the hook
keeps working below git 2.31.

Added $main_repo_root/{.venv,.warehouse-venv}/bin/custodian-multi to the
binary candidates; for a plain checkout main_repo_root == repo_root and
they are harmless duplicates. Left the `command -v` PATH-first ordering
alone — it changes nothing in either environment today (Windows has no
repo venv, so PATH is the only candidate; WSL has no PATH entry, so the
venv already wins).

The audit target stays "$repo_root": we audit the content being pushed,
which is the worktree.

Verified from all three repo shapes — worktree, plain clone at top level
(how git invokes hooks), and plain clone from a subdirectory — each
resolving to the same main_repo_root/workspace_root and finding the
artifact. workspace_root for a plain clone is byte-identical to before,
so non-worktree pushes are unaffected. End-to-end run of the hook from a
worktree with REPOGRAPH_BOUNDARY_ARTIFACT_FILE unset now reaches the
audit instead of failing closed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main already carries an equivalent workspace_root fix via #491, which
bundled the same worktree discovery bug. Resolved in favour of main's
version (git_common_dir/main_clone_root) — no reason to prefer my
phrasing of an identical fix.

What survives from this branch is the part main does NOT have: the
custodian-multi candidate list still omits the main clone's venv, so a
worktree push on a box whose only custodian-multi lives at
<clone>/.venv/bin — no PATH entry, no sibling Custodian checkout, which
is the fleet's layout — still fails "custodian-multi not found". Added
main_clone_root/{.venv,.warehouse-venv} using main's variable name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant