#15209·gitbutler

`but commit` never runs pre-commit / commit-msg / post-commit hooks (no call site in the CLI)

Author: artileCreated Aug 6, 2026Updated Sep 8, 2026

Summary

but commit never runs the pre-commit, commit-msg or post-commit hooks. Not "runs them and ignores the result" — the CLI has no call site for them at all. For a repository whose quality gates live in a pre-commit hook, this means committing through but silently skips every check that committing through git enforces.

Please run the commit-time hooks from the CLI, with a --no-hooks / --no-verify opt-out, the way but push and but pr new already do.

Evidence

Measured on but 0.22.0 (installed via brew install --cask gitbutler), on a repository that sets core.hooksPath=.githooks.

The hook was instrumented to append to a log file, regenerate a docs index and git add it — the shape of a real gate hook:

  • but commit -b exp-hooks2 -m "..." wmCreated commit vtp on new branch 'exp-hooks2'. The log file was never created. The resulting commit contains one file; the file the hook stages is absent.
  • Control, same hook, same repository, git commit → the log file is written and git diff --cached --name-only shows both the edited file and the file the hook staged.

The source agrees. pre_commit_with_tree() in crates/gitbutler-repo/src/hooks.rs has exactly one caller — crates/but-api/src/legacy/repo.rs, the desktop application's API. Searching crates/but/src for a pre-commit call site returns nothing. commit_msg() and post_commit() in the same file are reachable only from that same desktop path, so all three are CLI-dead. --no-hooks exists on but push and but pr new, but there is no flag on but commit to turn hooks on, because there is nothing to turn on.

Second, smaller problem — worth fixing in the same change

Even the desktop path discards what a hook stages. pre_commit_with_tree() backs up the index file, replaces it with the commit's tree, runs the hook, and then restores the backup with a plain rename:

rust
std::fs::rename(backup_path, index_path).context("failed to restore pre-commit index")?;

Any git add the hook performed is inside the index that just got overwritten. This is a common and load-bearing hook pattern — a formatter, a lockfile updater, or a generated-index regenerator stages its output so that it lands in the same commit. Wiring the CLI up to the current implementation would produce a hook that runs but whose output silently vanishes, which is arguably worse than not running it. Reconciling the hook's index writes back into the commit's change set would make the feature actually usable.

Why this matters for agent workflows

The CLI is the surface coding agents use, and the GitButler skill tells them to route every write through but and never through git. In a repository where correctness gates hang off pre-commit, following that instruction disables the gates — quietly, with no warning in the output and no flag in --help that hints hooks are inactive. A team adopting but for its agents would discover this only when something broken reaches CI.

Suggested shape

  • but commit, but amend, but squash and the other commit-creating commands run pre-commit and commit-msg before writing, and post-commit after.
  • --no-hooks (alias --no-verify) skips them, matching but push.
  • Hook failure aborts the operation and prints the hook's output, as but push already does for pre-push.
  • Changes a hook stages are folded into the commit rather than discarded.

Happy to test a build against a real gate hook setup if that helps.