Skip to content

Commit 07ce70c

Browse files
peyton-altclaude
andcommitted
fix(enable): explain core.hooksPath when hooks dir is not a directory
When core.hooksPath points at a non-directory (commonly /dev/null, the idiom for globally disabling git hooks), 'entire enable' failed with a raw 'mkdir /dev/null: not a directory' error that users read as a crash. Detect the case before MkdirAll and return guidance naming core.hooksPath, how to find where it is set, and how to unset or override it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 2a88eba commit 07ce70c

2 files changed

Lines changed: 53 additions & 0 deletions

File tree

cmd/entire/cli/strategy/hooks.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,13 @@ func InstallGitHook(ctx context.Context, silent, localDev, absolutePath bool) (i
294294
return 0, err
295295
}
296296

297+
if info, statErr := os.Stat(hooksDir); statErr == nil && !info.IsDir() {
298+
return 0, fmt.Errorf("git resolves the hooks directory to %s, which is not a directory — core.hooksPath is likely set to disable git hooks\n"+
299+
"Entire requires git hooks to capture sessions. See where it is set with:\n"+
300+
" git config --show-origin --get-all core.hooksPath\n"+
301+
"then unset it (git config --global --unset core.hooksPath) or override for this repo (git config core.hooksPath .git/hooks) and re-run 'entire enable'", hooksDir)
302+
}
303+
297304
if err := os.MkdirAll(hooksDir, 0o755); err != nil { //nolint:gosec // Git hooks require executable permissions
298305
return 0, fmt.Errorf("failed to create hooks directory: %w", err)
299306
}

cmd/entire/cli/strategy/hooks_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,52 @@ func TestGetHooksDirInPath_CoreHooksPath(t *testing.T) {
314314
}
315315
}
316316

317+
func TestInstallGitHook_HooksPathNotADirectory(t *testing.T) {
318+
// core.hooksPath pointing at a non-directory (commonly /dev/null, the
319+
// "disable git hooks globally" idiom) must fail with guidance naming
320+
// core.hooksPath, not a raw mkdir error.
321+
tmpDir := t.TempDir()
322+
ctx := context.Background()
323+
324+
cmd := exec.CommandContext(ctx, "git", "init")
325+
cmd.Dir = tmpDir
326+
if err := cmd.Run(); err != nil {
327+
t.Fatalf("failed to init git repo: %v", err)
328+
}
329+
330+
hooksPath := "/dev/null"
331+
if runtime.GOOS == "windows" {
332+
hooksPath = filepath.Join(tmpDir, "not-a-dir")
333+
if err := os.WriteFile(hooksPath, []byte("x"), 0o600); err != nil {
334+
t.Fatalf("failed to create non-directory hooks path: %v", err)
335+
}
336+
}
337+
cmd = exec.CommandContext(ctx, "git", "config", "core.hooksPath", hooksPath)
338+
cmd.Dir = tmpDir
339+
if err := cmd.Run(); err != nil {
340+
t.Fatalf("failed to set core.hooksPath: %v", err)
341+
}
342+
343+
t.Chdir(tmpDir)
344+
ClearHooksDirCache()
345+
paths.ClearWorktreeRootCache()
346+
347+
_, err := InstallGitHook(ctx, true, false, false)
348+
if err == nil {
349+
t.Fatal("InstallGitHook() should fail when hooks path is not a directory")
350+
}
351+
msg := err.Error()
352+
if !strings.Contains(msg, "core.hooksPath") {
353+
t.Errorf("error should name core.hooksPath, got: %s", msg)
354+
}
355+
if !strings.Contains(msg, hooksPath) {
356+
t.Errorf("error should include the resolved hooks path %s, got: %s", hooksPath, msg)
357+
}
358+
if !strings.Contains(msg, "git config") {
359+
t.Errorf("error should tell the user how to inspect/fix the setting, got: %s", msg)
360+
}
361+
}
362+
317363
func TestInstallGitHook_WorktreeInstallsInCommonHooks(t *testing.T) {
318364
mainRepo, worktreeDir := initHooksWorktreeRepo(t)
319365
t.Chdir(worktreeDir)

0 commit comments

Comments
 (0)