Skip to content

Install hooks to core.hooksPath instead of refusing#1692

Closed
domenkozar wants to merge 1 commit into
j178:masterfrom
domenkozar:install-hooks-path-idempotent
Closed

Install hooks to core.hooksPath instead of refusing#1692
domenkozar wants to merge 1 commit into
j178:masterfrom
domenkozar:install-hooks-path-idempotent

Conversation

@domenkozar

Copy link
Copy Markdown
Contributor

Summary

  • When core.hooksPath is set, install hooks to that directory instead of bailing with "Cowardly refusing"
  • This makes prek install idempotent and ensures hooks end up where git will actually look for them
  • If the configured path is not writable, bail with a clear error message

Motivation

Tools like devenv set core.hooksPath to manage git hooks. When prek is used as one of those hooks, running prek install a second time fails because it sees core.hooksPath is already set. The current behavior also means hooks installed to .git/hooks would never run when core.hooksPath points elsewhere.

Instead of refusing, prek should install to the configured path (where git will actually look for hooks). This resolves #23.

Test plan

  • prek install with core.hooksPath unset works as before
  • prek install with core.hooksPath set to a writable directory installs hooks there
  • Running prek install twice with core.hooksPath set succeeds (idempotent)
  • prek install with core.hooksPath set to a read only directory gives a clear error

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings February 25, 2026 11:20
@domenkozar domenkozar requested a review from j178 as a code owner February 25, 2026 11:20
@domenkozar

Copy link
Copy Markdown
Contributor Author

@j178 we switched over to prek, but this one is annoying because prek wants to force not setting core.hooksPath but some other tools want to set it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR changes prek install to install hooks to the directory specified by core.hooksPath when set, instead of refusing to proceed with a "Cowardly refusing" error. This resolves issue #23 and makes prek install idempotent when used with tools like devenv that manage git hooks via core.hooksPath.

Changes:

  • Refactored has_hooks_path_set() to get_hooks_path() which returns the configured path as Option<PathBuf> instead of a boolean
  • Removed the "cowardly refusing" check that prevented installation when core.hooksPath was set
  • Added logic to install hooks to the configured core.hooksPath directory with a write permission check

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
crates/prek/src/git.rs Refactored has_hooks_path_set() to get_hooks_path() which returns Option<PathBuf> with UTF-8 validation
crates/prek/src/cli/install.rs Removed the "cowardly refusing" check and added logic to install to core.hooksPath when set, with write permission validation

Comment thread crates/prek/src/cli/install.rs Outdated
Comment thread crates/prek/src/cli/install.rs Outdated
Comment thread crates/prek/src/cli/install.rs Outdated
Comment thread crates/prek/src/git.rs
Comment thread crates/prek/src/cli/install.rs Outdated
@prek-ci-bot

prek-ci-bot Bot commented Feb 25, 2026

Copy link
Copy Markdown

📦 Cargo Bloat Comparison

Binary size change: +0.00% (23.9 MiB → 23.9 MiB)

Expand for cargo-bloat output

Head Branch Results

 File  .text    Size             Crate Name
 0.3%   0.7% 71.3KiB              prek prek::languages::<impl prek::config::Language>::run::{{closure}}::{{closure}}
 0.3%   0.6% 65.7KiB             prek? <prek::cli::Command as clap_builder::derive::Subcommand>::augment_subcommands
 0.3%   0.6% 65.6KiB              prek prek::languages::<impl prek::config::Language>::run::{{closure}}::{{closure}}
 0.2%   0.5% 51.2KiB annotate_snippets annotate_snippets::renderer::render::render
 0.2%   0.5% 50.5KiB              prek prek::languages::<impl prek::config::Language>::install::{{closure}}
 0.2%   0.4% 41.7KiB              prek prek::cli::run::run::run::{{closure}}
 0.2%   0.4% 38.8KiB              prek prek::run::{{closure}}
 0.1%   0.3% 31.2KiB             prek? <prek::cli::RunArgs as clap_builder::derive::Args>::augment_args
 0.1%   0.3% 28.5KiB      serde_saphyr saphyr_parser_bw::scanner::Scanner<T>::fetch_more_tokens
 0.1%   0.2% 24.6KiB             prek? <prek::config::_::<impl serde_core::de::Deserialize for prek::config::Config>::deserialize::__Visitor as serde_core::de::Visitor>::visit_map
 0.1%   0.2% 22.8KiB      serde_saphyr saphyr_parser_bw::scanner::Scanner<T>::fetch_more_tokens
 0.1%   0.2% 22.7KiB              prek <prek::languages::ruby::ruby::Ruby as prek::languages::LanguageImpl>::install::{{closure}}
 0.1%   0.2% 22.3KiB              prek prek::hooks::meta_hooks::MetaHooks::run::{{closure}}
 0.1%   0.2% 22.3KiB              prek prek::hooks::meta_hooks::MetaHooks::run::{{closure}}
 0.1%   0.2% 21.1KiB      clap_builder clap_builder::parser::parser::Parser::get_matches_with
 0.1%   0.2% 20.0KiB               std core::ptr::drop_in_place<prek::languages::<impl prek::config::Language>::install::{{closure}}>
 0.1%   0.2% 19.4KiB   cargo_metadata? <cargo_metadata::_::<impl serde_core::de::Deserialize for cargo_metadata::Package>::deserialize::__Visitor as serde_core::de::Visitor>::visit_map
 0.1%   0.2% 19.2KiB              prek prek::archive::unzip::{{closure}}
 0.1%   0.2% 18.6KiB     serde_saphyr? <serde_saphyr::de::YamlDeserializer as serde_core::de::Deserializer>::deserialize_map
 0.1%   0.2% 18.6KiB              prek prek::cli::run::filter::collect_files_from_args::{{closure}}
38.3%  91.8%  9.2MiB                   And 21170 smaller methods. Use -n N to show more.
41.7% 100.0% 10.0MiB                   .text section size, the file size is 23.9MiB

Base Branch Results

 File  .text    Size             Crate Name
 0.3%   0.7% 71.3KiB              prek prek::languages::<impl prek::config::Language>::run::{{closure}}::{{closure}}
 0.3%   0.7% 66.4KiB             prek? <prek::cli::Command as clap_builder::derive::Subcommand>::augment_subcommands
 0.3%   0.6% 65.6KiB              prek prek::languages::<impl prek::config::Language>::run::{{closure}}::{{closure}}
 0.2%   0.5% 51.2KiB annotate_snippets annotate_snippets::renderer::render::render
 0.2%   0.5% 50.5KiB              prek prek::languages::<impl prek::config::Language>::install::{{closure}}
 0.2%   0.4% 41.2KiB              prek prek::cli::run::run::run::{{closure}}
 0.2%   0.4% 38.8KiB              prek prek::run::{{closure}}
 0.1%   0.3% 32.0KiB             prek? <prek::cli::RunArgs as clap_builder::derive::Args>::augment_args
 0.1%   0.3% 28.5KiB      serde_saphyr saphyr_parser_bw::scanner::Scanner<T>::fetch_more_tokens
 0.1%   0.2% 24.7KiB             prek? <prek::config::_::<impl serde_core::de::Deserialize for prek::config::Config>::deserialize::__Visitor as serde_core::de::Visitor>::visit_map
 0.1%   0.2% 22.8KiB      serde_saphyr saphyr_parser_bw::scanner::Scanner<T>::fetch_more_tokens
 0.1%   0.2% 22.7KiB              prek <prek::languages::ruby::ruby::Ruby as prek::languages::LanguageImpl>::install::{{closure}}
 0.1%   0.2% 22.7KiB              prek prek::hooks::meta_hooks::MetaHooks::run::{{closure}}
 0.1%   0.2% 22.4KiB              prek prek::hooks::meta_hooks::MetaHooks::run::{{closure}}
 0.1%   0.2% 21.1KiB      clap_builder clap_builder::parser::parser::Parser::get_matches_with
 0.1%   0.2% 20.0KiB               std core::ptr::drop_in_place<prek::languages::<impl prek::config::Language>::install::{{closure}}>
 0.1%   0.2% 19.5KiB              prek prek::archive::unzip::{{closure}}
 0.1%   0.2% 19.4KiB   cargo_metadata? <cargo_metadata::_::<impl serde_core::de::Deserialize for cargo_metadata::Package>::deserialize::__Visitor as serde_core::de::Visitor>::visit_map
 0.1%   0.2% 18.7KiB     serde_saphyr? <serde_saphyr::de::YamlDeserializer as serde_core::de::Deserializer>::deserialize_map
 0.1%   0.2% 18.6KiB     serde_saphyr? <serde_saphyr::de::YamlDeserializer as serde_core::de::Deserializer>::deserialize_map
38.3%  91.8%  9.1MiB                   And 21088 smaller methods. Use -n N to show more.
41.8% 100.0% 10.0MiB                   .text section size, the file size is 23.9MiB

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.

Comment thread Cargo.toml Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

Comment thread crates/prek/src/cli/install.rs Outdated
Comment thread crates/prek/src/cli/install.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.

Comment thread crates/prek/tests/install.rs Outdated
@domenkozar domenkozar force-pushed the install-hooks-path-idempotent branch from 239062c to 63501c3 Compare February 25, 2026 12:26
@domenkozar domenkozar requested a review from Copilot February 25, 2026 12:26
@domenkozar domenkozar force-pushed the install-hooks-path-idempotent branch 2 times, most recently from 7c00b08 to fa5d0e8 Compare February 25, 2026 12:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.

Comment thread crates/prek/src/cli/install.rs Outdated
@domenkozar domenkozar force-pushed the install-hooks-path-idempotent branch from fa5d0e8 to 5958d74 Compare February 25, 2026 12:40
When `core.hooksPath` is set, install hooks to that directory instead
of bailing. This makes `prek install` idempotent and ensures hooks
end up where git will actually look for them.

Relative `core.hooksPath` values are resolved against the git common
dir, matching git's behavior. If the configured path is not writable,
bail with a clear message using `faccess` for proper cross-platform
writability checks.

Closes j178#23

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 25, 2026 12:42
@domenkozar domenkozar force-pushed the install-hooks-path-idempotent branch from 5958d74 to 44175af Compare February 25, 2026 12:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated no new comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated no new comments.

@domenkozar

Copy link
Copy Markdown
Contributor Author

@j178 curious what you think about this solution :)

@j178

j178 commented Feb 26, 2026

Copy link
Copy Markdown
Owner

Thanks! I haven’t had much time for open source lately, but I’ll get back to you soon.

@janlarres

Copy link
Copy Markdown

I don't think silently writing to a global hooks path is a good idea. This will obviously affect all repositories which may not be desired, especially since prek will return an error if a repository doesn't have a config file. It may also overwrite a pre-existing global hook.

For example, in my case I have a hook script that you symlink to the hook names like pre-commit and which then runs all files in the respective *.d directory like pre-commit.d, in both the global hooks dir and the local one. This means that prek works fine for my setup as long as I unset core.hooksPath, run prek install, and set the path again, but this change would silently break all of my other pre-commit hooks by overwriting the symlink.

Personally my ideal solution would be if I could set a global config option to run prek install despite core.hooksPath being set because my setup is designed to deal with that, but this may not work for everyone.

@domenkozar

Copy link
Copy Markdown
Contributor Author

I don't think silently writing to a global hooks path is a good idea. This will obviously affect all repositories which may not be desired, especially since prek will return an error if a repository doesn't have a config file. It may also overwrite a pre-existing global hook.

For example, in my case I have a hook script that you symlink to the hook names like pre-commit and which then runs all files in the respective *.d directory like pre-commit.d, in both the global hooks dir and the local one. This means that prek works fine for my setup as long as I unset core.hooksPath, run prek install, and set the path again, but this change would silently break all of my other pre-commit hooks by overwriting the symlink.

Personally my ideal solution would be if I could set a global config option to run prek install despite core.hooksPath being set because my setup is designed to deal with that, but this may not work for everyone.

I understand why your workflow works the way it does, but it feels backwards that git providers a way to tell where to install git hooks and then prek just bails out when that option is set.

I'd suggest that by default prek supports the option, optionally warns about where it's installing the hook and allows a knob to disable respecting the option for such cases. Would that work for you?

@domenkozar

domenkozar commented Mar 1, 2026

Copy link
Copy Markdown
Contributor Author

The issue I'm having is that https://github.com/cachix/git-hooks.nix sets core.hooksPath to .git/hooks so running prek install bails out where pre-commit respects that.

@j178

j178 commented Mar 1, 2026

Copy link
Copy Markdown
Owner

prek install sets core.hooksPath to .git/hooks

No, I don’t think prek changes any Git configuration. What makes you think that?

@domenkozar

Copy link
Copy Markdown
Contributor Author

prek install sets core.hooksPath to .git/hooks

No, I don’t think prek changes any Git configuration. What makes you think that?

My mistake, I've changed the description of what happens :)

@j178

j178 commented Mar 1, 2026

Copy link
Copy Markdown
Owner

where pre-commit respects that

This doesn’t seem right either. Since prek uses the same logic as pre-commit, it should bail out too. Could you take a look at that?

git initgit config core.hooksPath hookspre-commit install
[ERROR] Cowardly refusing to install hooks with `core.hooksPath` set.
hint: `git config --unset-all core.hooksPath`

domenkozar added a commit to cachix/devenv that referenced this pull request Mar 1, 2026
git-hooks installation sets core.hooksPath to .git/hooks which doesn't
work with prek. Unset it before installing hooks.

j178/prek#1692

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@j178

j178 commented Mar 1, 2026

Copy link
Copy Markdown
Owner

@domenkozar Could you take a look at #1723? It adds a --git-dir flag to prek install so you can force the hook installation target directory.

@domenkozar domenkozar closed this Mar 1, 2026
@codecov

codecov Bot commented Mar 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.34%. Comparing base (85eff56) to head (44175af).
⚠️ Report is 22 commits behind head on master.

Files with missing lines Patch % Lines
crates/prek/src/cli/install.rs 90.00% 2 Missing ⚠️
crates/prek/src/git.rs 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1692      +/-   ##
==========================================
- Coverage   91.57%   90.34%   -1.23%     
==========================================
  Files          96       96              
  Lines       18842    18860      +18     
==========================================
- Hits        17255    17040     -215     
- Misses       1587     1820     +233     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

Correctly support core.hooksPath

4 participants