script/install-precommit fails in a linked worktree: .git is a file, not a directory #129

Open
opened 2026-08-09 16:39:25 +02:00 by clawbot · 0 comments
Collaborator

script/install-precommit hardcodes the hooks path:

hook=".git/hooks/pre-commit"
printf '#!/bin/sh\nset -e\nscript/precommit\n' > "$hook"

In a linked worktree .git is a file, not a directory. Verified in this repo:

$ cat .claude/worktrees/agent-.../.git
gitdir: /srv/code/dnswatcher/.git/worktrees/agent-...

So the redirect targets a path under a regular file and make hooks fails outright there.

Why it matters here

All implementation and review work on this repo happens in throwaway linked worktrees — that is a standing rule, since concurrent sessions collided in the shared checkout once already. So make hooks is broken in exactly the environment every agent actually works in. The implementer of PR #128 hit this and had to commit with --no-verify, running make check explicitly instead.

Note the hook is not entirely absent from worktrees: git resolves hooks via the common git dir, so a hook installed once from the main checkout does run in linked worktrees. The defect is specifically that install-precommit cannot be run from one. That distinction matters — it means the exposure is "agents can't install the hook", not necessarily "no agent has ever had one".

Definition of done

  1. script/install-precommit works from the main checkout and from a linked worktree. git rev-parse --git-path hooks resolves correctly in both and is the idiomatic fix; create the directory if absent.
  2. Verify by running it from both, and confirm the installed hook actually fires on a commit in each. A path that merely exists is not the test — make a throwaway commit and watch the hook run.
  3. script/install-precommit stays POSIX sh (#!/bin/sh, set -eu, no bashisms); sh -n clean; keeps the $(cd "$(dirname "$0")/.." && pwd -P) idiom.
  4. Behaviour is unchanged where it already worked — do not silently relocate the hook for existing checkouts.
  5. make check green; TODO.md updated in the same commit.

Commit title ends with (closes #N).

Constraints

  • Do not modify .golangci.yml (sha256 021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb) or the golangci-lint pin.
  • DNS is never mocked in this repository; nothing here touches test behaviour.
  • Lint runs may be void (#121): void if the output says parallel golangci-lint is running, or names any path starting with ../, or any absolute path outside your worktree.

Coordination

Touches script/install-precommit only — no overlap with #115/#122 (script/cibuild), #117 (script/bootstrap), #119 (script/fmt, script/fmt-check), or #121/#128 (script/lint).

script/install-precommit is byte-identical across repos, so this belongs upstream in the shared template as well; it is already known there as one of the Scripts to Rule Them All defects.

`script/install-precommit` hardcodes the hooks path: ```sh hook=".git/hooks/pre-commit" printf '#!/bin/sh\nset -e\nscript/precommit\n' > "$hook" ``` In a **linked worktree** `.git` is a file, not a directory. Verified in this repo: ``` $ cat .claude/worktrees/agent-.../.git gitdir: /srv/code/dnswatcher/.git/worktrees/agent-... ``` So the redirect targets a path under a regular file and `make hooks` fails outright there. ## Why it matters here All implementation and review work on this repo happens in throwaway linked worktrees — that is a standing rule, since concurrent sessions collided in the shared checkout once already. So `make hooks` is broken in exactly the environment every agent actually works in. The implementer of [PR #128](https://git.eeqj.de/sneak/dnswatcher/pulls/128) hit this and had to commit with `--no-verify`, running `make check` explicitly instead. Note the hook is not entirely absent from worktrees: git resolves hooks via the **common** git dir, so a hook installed once from the main checkout does run in linked worktrees. The defect is specifically that `install-precommit` cannot be run *from* one. That distinction matters — it means the exposure is "agents can't install the hook", not necessarily "no agent has ever had one". ## Definition of done 1. `script/install-precommit` works from the main checkout **and** from a linked worktree. `git rev-parse --git-path hooks` resolves correctly in both and is the idiomatic fix; create the directory if absent. 2. Verify by running it from both, and confirm the installed hook actually fires on a commit in each. A path that merely *exists* is not the test — make a throwaway commit and watch the hook run. 3. `script/install-precommit` stays POSIX `sh` (`#!/bin/sh`, `set -eu`, no bashisms); `sh -n` clean; keeps the `$(cd "$(dirname "$0")/.." && pwd -P)` idiom. 4. Behaviour is unchanged where it already worked — do not silently relocate the hook for existing checkouts. 5. `make check` green; `TODO.md` updated in the same commit. Commit title ends with ` (closes #N)`. ## Constraints - Do not modify `.golangci.yml` (sha256 `021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb`) or the golangci-lint pin. - DNS is never mocked in this repository; nothing here touches test behaviour. - Lint runs may be void (#121): void if the output says `parallel golangci-lint is running`, or names any path starting with `../`, or any absolute path outside your worktree. ## Coordination Touches `script/install-precommit` only — no overlap with #115/#122 (`script/cibuild`), #117 (`script/bootstrap`), #119 (`script/fmt`, `script/fmt-check`), or #121/#128 (`script/lint`). `script/install-precommit` is byte-identical across repos, so this belongs upstream in the shared template as well; it is already known there as one of the Scripts to Rule Them All defects.
clawbot added this to the 1.0 milestone 2026-08-09 16:39:25 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/dnswatcher#129