Symptom
graphify hook install and graphify hook status both crash with NotADirectoryError when run from a linked git worktree (i.e. a directory created via git worktree add).
$ graphify hook install
Traceback (most recent call last):
File ".../graphify/__main__.py", line 1381, in main
print(hook_install(Path(".")))
File ".../graphify/hooks.py", line 246, in install
hooks_dir = _hooks_dir(root)
File ".../graphify/hooks.py", line 201, in _hooks_dir
d.mkdir(exist_ok=True)
NotADirectoryError: [Errno 20] Not a directory: '/path/to/worktree/.git/hooks'
graphify hook status and graphify hook uninstall fail identically.
Regression of #516
This is the same crash as #516 ("无法在 git worktree 项目运行 hooks 命令"), which was closed in April 2026 with the comment:
Fixed in v0.4.32: _hooks_dir() now uses git rev-parse --path-format=absolute --git-path hooks instead of git config core.hooksPath. Git resolves this correctly for both normal repos and worktree checkouts, pointing to the shared hooks directory in the main repo's .git/hooks. Includes regression tests for install/uninstall from a worktree.
However, the current v7 (default) branch source shows _hooks_dir still uses the broken path-join approach — no git rev-parse call:
https://github.com/safishamsi/graphify/blob/v7/graphify/hooks.py
def _hooks_dir(root: Path) -> Path:
"""Return the git hooks directory, respecting core.hooksPath if set (e.g. Husky)."""
try:
cfg = configparser.RawConfigParser()
cfg.read(root / ".git" / "config", encoding="utf-8")
# ... core.hooksPath handling (only works when .git is a directory) ...
except (configparser.Error, OSError) as exc:
...
d = root / ".git" / "hooks"
d.mkdir(exist_ok=True) # <-- NotADirectoryError here when .git is a file
return d
Either the v0.4.32 fix was reverted, or it never landed on the release branch and only existed in a feature branch. Bug reproduces on the current pypi release (graphifyy 0.7.13) and is still present in v7 HEAD as of today.
Reproduction
git init main-repo && cd main-repo
echo "hi" > a.txt && git add . && git commit -m init
git worktree add ../wt
cd ../wt
graphify hook install # crashes
graphify hook status # also crashes
In a linked worktree, .git is a regular file containing gitdir: <main-repo>/.git/worktrees/<name>, not a directory. _git_root() matches it via (parent / ".git").exists(), then _hooks_dir() does root / ".git" / "hooks" and the mkdir call fails.
Suggested Fix
Use git's own machinery to locate the hooks dir, which transparently handles worktrees:
import subprocess
def _hooks_dir(root: Path) -> Path:
"""Return the git hooks dir; handles worktrees via git rev-parse."""
try:
hooks_path = subprocess.check_output(
["git", "rev-parse", "--path-format=absolute", "--git-path", "hooks"],
cwd=root,
text=True,
).strip()
p = Path(hooks_path)
p.mkdir(parents=True, exist_ok=True)
return p
except subprocess.CalledProcessError:
# Fall back to the current logic for non-git or unusual cases
d = root / ".git" / "hooks"
d.mkdir(exist_ok=True)
return d
git rev-parse --git-path hooks returns:
<repo>/.git/hooks in a normal repo
<main-repo>/.git/hooks from any linked worktree (the shared hooks dir, which is the correct location per git's design)
- A custom path when
core.hooksPath is set
_git_root() should also be updated to use git rev-parse --show-toplevel rather than walking up looking for .git, but the _hooks_dir fix alone resolves the crash.
Workaround
For anyone hitting this now, install hooks manually to the common dir:
from graphify import hooks
from pathlib import Path
import subprocess
common = Path(subprocess.check_output(["git", "rev-parse", "--git-common-dir"], text=True).strip())
hooks_dir = common / "hooks"
print(hooks._install_hook(hooks_dir, "post-commit", hooks._HOOK_SCRIPT, hooks._HOOK_MARKER))
print(hooks._install_hook(hooks_dir, "post-checkout", hooks._CHECKOUT_SCRIPT, hooks._CHECKOUT_MARKER))
To uninstall manually: rm $(git rev-parse --git-common-dir)/hooks/post-commit $(git rev-parse --git-common-dir)/hooks/post-checkout.
Environment
- macOS 25.3.0 (Darwin arm64)
- Python 3.14.5 (pipx install)
- graphifyy 0.7.13 (latest pypi: 0.7.19; bug also visible in v7 HEAD)
- Worktree created via
git worktree add
Symptom
graphify hook installandgraphify hook statusboth crash withNotADirectoryErrorwhen run from a linked git worktree (i.e. a directory created viagit worktree add).graphify hook statusandgraphify hook uninstallfail identically.Regression of #516
This is the same crash as #516 ("无法在 git worktree 项目运行 hooks 命令"), which was closed in April 2026 with the comment:
However, the current
v7(default) branch source shows_hooks_dirstill uses the broken path-join approach — nogit rev-parsecall:https://github.com/safishamsi/graphify/blob/v7/graphify/hooks.py
Either the v0.4.32 fix was reverted, or it never landed on the release branch and only existed in a feature branch. Bug reproduces on the current pypi release (graphifyy 0.7.13) and is still present in
v7HEAD as of today.Reproduction
In a linked worktree,
.gitis a regular file containinggitdir: <main-repo>/.git/worktrees/<name>, not a directory._git_root()matches it via(parent / ".git").exists(), then_hooks_dir()doesroot / ".git" / "hooks"and themkdircall fails.Suggested Fix
Use git's own machinery to locate the hooks dir, which transparently handles worktrees:
git rev-parse --git-path hooksreturns:<repo>/.git/hooksin a normal repo<main-repo>/.git/hooksfrom any linked worktree (the shared hooks dir, which is the correct location per git's design)core.hooksPathis set_git_root()should also be updated to usegit rev-parse --show-toplevelrather than walking up looking for.git, but the_hooks_dirfix alone resolves the crash.Workaround
For anyone hitting this now, install hooks manually to the common dir:
To uninstall manually:
rm $(git rev-parse --git-common-dir)/hooks/post-commit $(git rev-parse --git-common-dir)/hooks/post-checkout.Environment
git worktree add