Home > User Guide > Git Hooks
Stave provides native Git hook management, allowing projects to run Stave targets automatically when Git events occur (commit, push, etc.).
Git hooks are scripts that Git executes before or after events such as commit, push, and merge. Stave can manage these hooks, replacing external tools like Husky or pre-commit.
Benefits:
- No external dependencies (Node.js, Python)
- Hook behavior defined as Stave targets
- Declarative configuration in
stave.yaml - Portable POSIX-compatible scripts
- Add hooks configuration to
stave.yaml:
hooks:
pre-commit:
- target: Fmt
- target: Lint
pre-push:
- target: Test- Install the hooks:
stave --hooks install- Commit as normal. The configured targets run automatically.
Define hooks in your project's stave.yaml:
hooks:
pre-commit:
- target: fmt
- target: lint
args: ["--fast"]
pre-push:
- target: test
args: ["./..."]
commit-msg:
- target: validate-commit-message
passStdin: trueEach hook entry supports the following options:
| Option | Type | Description |
|---|---|---|
target |
string | Name of the Stave target to run (required) |
args |
[]string | Additional arguments passed to the target |
workdir |
string | Working directory for the target invocation |
passStdin |
bool | Forward stdin from Git to the target (see below) |
The workdir option allows you to specify the directory in which the Stave target should be executed. This is useful if your project structure requires certain targets to run from a specific subdirectory.
If workdir is a relative path, it is resolved relative to the directory containing the stave.yaml (or the configuration file being used). If no configuration file is used, it is resolved relative to the current working directory.
Example:
hooks:
pre-commit:
- target: Lint
workdir: ./frontendStave supports all standard Git hooks:
| Hook | When It Runs |
|---|---|
pre-commit |
Before commit message editor opens |
prepare-commit-msg |
After default message, before editor |
commit-msg |
After commit message is entered |
post-commit |
After commit completes |
pre-push |
Before push to remote |
pre-rebase |
Before rebase starts |
post-checkout |
After checkout completes |
post-merge |
After merge completes |
pre-receive |
Server-side, before refs are updated |
post-receive |
Server-side, after refs are updated |
Unrecognized hook names generate a warning but are still installed.
List configured hooks (default when no subcommand given):
stave --hooksOutput:
Configured Git hooks:
pre-commit:
- fmt
- lint --fast
pre-push:
- test ./...
All 2 hook(s) installed.
Display setup instructions for new projects:
stave --hooks initInstall hook scripts to the Git repository:
stave --hooks installThis writes executable scripts to .git/hooks/ (or the directory configured via core.hooksPath).
Flags:
| Flag | Description |
|---|---|
--force |
Overwrite existing non-Stave hooks |
If an existing hook was not installed by Stave, the command fails unless --force is specified.
Remove Stave-managed hooks:
stave --hooks uninstallOnly removes hooks that were installed by Stave (identified by a marker comment).
Flags:
| Flag | Description |
|---|---|
--all |
Remove all Stave-managed hooks (not just configured) |
Alias for stave --hooks (no subcommand). Lists configured hooks and their installation status.
Execute targets for a specific hook. This is called by the generated hook scripts:
stave --hooks run pre-commit -- "$@"You can run this manually for debugging:
stave --hooks run pre-commitControl hook behavior through environment variables:
| Variable | Effect |
|---|---|
STAVE_HOOKS=0 |
Disable all hooks (exit silently with 0) |
STAVE_HOOKS=debug |
Enable shell tracing (set -x) in hook scripts |
STAVE_QUIET=1 |
Suppress decorative output (auto-detected in CI) |
To temporarily skip hooks:
STAVE_HOOKS=0 git commit -m "WIP"Or set globally in your shell profile to disable hooks on a specific machine.
Enable verbose output:
STAVE_HOOKS=debug git commit -m "test"Decorative output (hook run messages, test headers) is automatically suppressed when running in CI environments. Stave detects CI via these environment variables:
CIGITHUB_ACTIONSGITLAB_CIJENKINS_URLCIRCLECIBUILDKITE
To force quiet mode outside CI:
STAVE_QUIET=1 stave testHook scripts source an optional user init script before running Stave. This is useful for:
- Initializing version managers (nvm, pyenv, etc.)
- Adjusting
PATHfor GUI Git clients - Machine-specific environment setup
Location:
${XDG_CONFIG_HOME:-$HOME/.config}/stave/hooks/init.sh
Example init.sh:
# Load nvm for Node.js version management
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
# Ensure Go is on PATH
export PATH="$PATH:/usr/local/go/bin:$HOME/go/bin"Stave generates POSIX-compatible shell scripts. Example pre-commit hook:
#!/bin/sh
# Installed by Stave: DO NOT EDIT BY HAND
# Optional user-level initialization (PATH, version managers, etc.)
init_script="${XDG_CONFIG_HOME:-$HOME/.config}/stave/hooks/init.sh"
[ -f "$init_script" ] && . "$init_script"
# Global toggle and debug controls
if [ "${STAVE_HOOKS-}" = "0" ]; then
exit 0
fi
[ "${STAVE_HOOKS-}" = "debug" ] && set -x
if command -v stave >/dev/null 2>&1; then
exec stave --hooks run pre-commit -- "$@"
else
echo "stave: 'stave' binary not found on PATH; skipping pre-commit hook." >&2
exit 0
fiKey properties:
- POSIX
shcompatible (no bash-specific features) - Graceful fallback if
staveis not on PATH - Respects
STAVE_HOOKSenvironment variable - Clearly marked as Stave-managed
If your repository uses core.hooksPath:
git config core.hooksPath .githooksStave respects this setting and installs hooks to the configured directory.
Some hooks receive data via stdin:
| Hook | Stdin Contains |
|---|---|
commit-msg |
Path to commit message file |
prepare-commit-msg |
Path to commit message file |
pre-receive |
List of refs being updated |
post-receive |
List of refs that were updated |
Use passStdin: true to forward stdin to your target:
hooks:
commit-msg:
- target: validate-commit-message
passStdin: trueTargets for a hook run sequentially in the order defined:
hooks:
pre-commit:
- target: fmt # Runs first
- target: lint # Runs second
- target: typecheck # Runs thirdExecution stops on the first failure. If lint fails, typecheck does not run:
stave: hook pre-commit failed at target lint (exit 1)
- Exit
0: Hook passes, Git operation proceeds - Non-zero exit: Hook fails, Git operation is blocked
//go:build stave
package main
import (
"github.com/yaklabco/stave/pkg/sh"
)
// Fmt formats Go code.
func Fmt() error {
return sh.RunV("go", "fmt", "./...")
}
// Lint runs the linter.
func Lint() error {
return sh.RunV("golangci-lint", "run")
}
// Test runs the test suite.
func Test() error {
return sh.RunV("go", "test", "./...")
}hooks:
pre-commit:
- target: Fmt
- target: Lint
pre-push:
- target: Teststave --hooks installNow every commit runs Fmt and Lint, and every push runs Test.
If your repository uses Husky or another hook manager that sets core.hooksPath, you must unset it before installing Stave hooks.
# Unset any custom hooks path (e.g., from Husky)
git config --unset core.hooksPath
# Install Stave-managed hooks
stave --hooks install# Uninstall Stave hooks
stave --hooks uninstall
# Restore Husky's hooks path
git config core.hooksPath .huskyFor projects that need to switch between hook systems frequently, you can add a Hooks target to your stavefile.go:
// SwitchHooks configures git hooks to use either "husky" or "stave" (native).
func SwitchHooks(system string) error {
switch strings.ToLower(system) {
case "husky":
_ = sh.Run("stave", "--hooks", "uninstall")
return sh.Run("git", "config", "core.hooksPath", ".husky")
case "stave":
_ = sh.Run("git", "config", "--unset", "core.hooksPath")
return sh.Run("stave", "--hooks", "install")
default:
return fmt.Errorf("unknown hooks system %q: use 'husky' or 'stave'", system)
}
}This enables:
stave SwitchHooks stave # Switch to native Stave hooks
stave SwitchHooks husky # Switch to HuskyTo permanently migrate from Husky to Stave hooks:
- Add
hooksconfiguration tostave.yamlbased on your.husky/*scripts - Unset the Husky hooks path:
git config --unset core.hooksPath - Install Stave hooks:
stave --hooks install - Remove Husky:
npm uninstall husky - Delete the
.husky/directory - Remove the
preparescript frompackage.json
- Configuration - Full configuration reference
- CLI Reference - Command-line flags
- Targets - Defining target functions
- Home