Skip to content

feat(scripts): create worktrees with new-worktree.sh and keep the shared hook bound to the primary checkout - #218

Merged
cayossarian merged 4 commits into
prerelease/3.8from
feat/worktree-tooling
Oct 24, 2026
Merged

cayossarian merged 4 commits into
prerelease/3.8from
feat/worktree-tooling

Conversation

@cayossarian

@cayossarian cayossarian commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Worktrees of this repository have been created with scripts/new-worktree.sh, from a branch that never reached main or a pre-release branch. This brings the script in, with the setup-hooks.sh fix it depends on and the instructions for both.

The fix matters on its own. Git hooks live in the repository's common directory and every worktree shares them, and pre-commit install writes the interpreter that runs it into the hook. setup-hooks.sh ran it wherever it was invoked, so running it in a worktree bound every checkout's commits to that worktree's virtual environment, and once the worktree was removed every commit failed with "pre-commit not found".

Change

  • setup-hooks.sh:
    • Installs the pre-commit hook only in the primary checkout. In a worktree it syncs that worktree's own environment and checks the shared hook, saying how to rebind it if it is not bound to the primary checkout, and leaves it alone either way.
    • --check-hook only checks, and exits non-zero when the hook is missing, bound to another environment, or bound to an interpreter that no longer exists.
    • Every run syncs. The .deps-installed stamp it gated on had been committed, so whether a fresh worktree got an environment at all depended on the order in which checkout wrote files. The stamp is removed from the repository and ignored.
  • scripts/new-worktree.sh <name> [<branch>]:
    • Fetches origin, fast-forwards local main where that is safe, and refuses when main holds commits origin/main lacks.
    • Installs the shared hook from the primary checkout first when it is missing or bound elsewhere, and refuses to create the worktree if it is still not bound.
    • Creates ../span-panel-api-<name> on a branch that tracks no remote, so a bare git push can never land on main, and links .env.
    • Prepares the worktree with the setup-hooks.sh beside the script rather than the worktree's own copy, because origin/main can predate the worktree-aware one and running that would rebind the shared hook.
  • DEVELOPMENT.md: the Git Hooks section describes both modes and --check-hook, and a new Worktrees section describes the script.
  • AGENTS.md: a Worktrees section. CLAUDE.md already imports AGENTS.md.

The integration's script comes in with SpanPanel/span#333.

Checks

  • --check-hook, exercised in a throwaway repository:
    • Exits 1 with its own message when no hook is installed (run from the primary checkout and from a worktree), when the hook is bound to another environment, when it is bound to the worktree's own environment, and when it is bound to the primary checkout's interpreter after that is gone.
    • Exits 0 when the hook is bound to the primary checkout, with the path quoted or not.
    • An unknown argument exits 2.
  • new-worktree.sh refusals: a name with a space and a call with no arguments exit 2. An existing target directory and an existing branch exit 1, before anything is fetched or created.
  • This branch's worktree was created by this script, which found the shared hook bound to the primary checkout and left it alone. --check-hook from the worktree agrees.
  • The scripts are byte-identical to the copies every worktree here has been created with since 2026-09-14.
  • shellcheck is clean on both scripts. All hooks pass over the branch: 1857 passed, 7 skipped, 9 xfailed.

Not covered

  • The branch always starts from origin/main. Work for a pre-release branch, this one included, is reset onto it by hand after the script runs.
  • Nothing tests the scripts automatically. The checks above were run by hand.

…orktree

Git hooks live in the repository's common directory and every worktree shares
them, and `pre-commit install` writes the interpreter that runs it into the
hook. setup-hooks.sh ran it wherever it was invoked, so running it in a
worktree bound every checkout's commits to that worktree's virtual
environment, and once the worktree was removed every commit failed with
"`pre-commit` not found". It now installs the hook only in the primary
checkout. A worktree checks that the shared hook is bound to the primary
checkout's environment and says how to rebind it if not, and leaves it alone
either way.

It also syncs on every run. The stamp file it gated on had been committed, so
a fresh worktree checked the stamp out and whether it got an environment at
all came down to the order in which checkout wrote files. The stamp is now
untracked and ignored.
Creating a worktree correctly takes several steps that are easy to skip, and
skipping them fails quietly. scripts/new-worktree.sh <name> [<branch>] does
them in order: it fetches origin, fast-forwards local main where that is safe
and refuses when main holds commits origin/main lacks, creates
../span-panel-api-<name> on a branch that tracks no remote so a bare push can
never land on main, links .env from the primary checkout, and runs
setup-hooks.sh inside it.

The worktree gets a virtual environment of its own rather than a link to the
primary checkout's. This repository installs its own distributions in
editable mode, each recording one checkout's source path, so a shared
environment imports whichever checkout synced last and tests pass against the
wrong code without an error.

Main is moved with update-ref rather than `branch --force`, which would also
rewrite its upstream, and a checked-out main is fast-forwarded only when it
has no uncommitted tracked changes. The setup script beside this one runs
rather than the new worktree's copy, because a branch from origin/main can
predate the worktree-aware setup.
…orktree

A worktree commits through the one hook in the common directory. When that hook
was missing, new-worktree.sh only warned after the fact and the worktree's commits
skipped every check without a word; when it was bound to an environment that no
longer exists, they failed.

setup-hooks.sh gains --check-hook, which exits non-zero when the shared hook is
missing or not bound to the primary checkout. new-worktree.sh runs it before
creating the worktree, installs the hook from the primary checkout when it fails,
and refuses to create the worktree if the hook is still not bound afterwards.
AGENTS.md gains a Worktrees section: create worktrees with
scripts/new-worktree.sh rather than `git worktree add` or a built-in worktree
tool, never share a virtual environment between checkouts, and install the
pre-commit hook from the primary checkout. CLAUDE.md already imports
AGENTS.md, so the section reaches every agent without further change.
@cayossarian
cayossarian merged commit 9385c1b into prerelease/3.8 Oct 24, 2026
5 checks passed
@cayossarian
cayossarian deleted the feat/worktree-tooling branch October 24, 2026 18:56
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