Ensure pre-commit hooks are wired up. This must work in worktrees too, where .git/ is a file (not a directory) and the real hooks path is shared with the main repo via core.hooksPath. Use git rev-parse --git-path so the check resolves correctly regardless:
# Is the framework on PATH?
pre-commit --version >/dev/null 2>&1 && echo "INSTALLED" || echo "NOT_INSTALLED"
# Is the hook actually wired into git's hook system?
PRE_COMMIT_HOOK_PATH=$(git rev-parse --git-path hooks/pre-commit)
COMMIT_MSG_HOOK_PATH=$(git rev-parse --git-path hooks/commit-msg)
test -f "$PRE_COMMIT_HOOK_PATH" && grep -q "pre-commit" "$PRE_COMMIT_HOOK_PATH" \
&& test -f "$COMMIT_MSG_HOOK_PATH" && grep -q "pre-commit" "$COMMIT_MSG_HOOK_PATH" \
&& echo "ACTIVE" || echo "INACTIVE"
-
If NOT_INSTALLED, tell the user once: "⚠️ pre-commit is not on PATH. Install it with pip install pre-commit so format/lint runs on every commit." Then continue (don't block).
-
If installed but INACTIVE, install it yourself — the project ships .pre-commit-config.yaml and make doctor is a no-op-on-already-installed wrapper around the same command:
pre-commit install -t pre-commit -t commit-msg --overwrite
Mention that you wired it up. Subsequent commits will run hooks automatically.
-
If both checks pass, no output needed.
-
Before the first commit, ensure the worktree can run the commit-msg
dependency. If apps/node_modules/.bin/commitlint is absent, install it
from apps/ before committing:
cd apps && pnpm install --frozen-lockfile
Retry the normal commit after the install; never bypass the hook.
-
After merging or rebasing the base branch, if apps/package.json or
apps/pnpm-lock.yaml changed and a hook reports
ERR_MODULE_NOT_FOUND even though apps/node_modules/.bin/commitlint
exists, refresh the workspace dependencies from apps/:
cd apps && pnpm install --frozen-lockfile
Retry the normal commit without bypassing hooks.
Why this matters: a missing hook lets lint regressions slip past local commits and only surface in CI (e.g. funlen / cognitive complexity on backend Go code). The hook catches them in <1s at commit time. See Makefile's doctor target for the idempotent install command.
Commit: Write a commit message following the format above. If changes span multiple concerns, consider separate commits.
When MERGE_HEAD exists or a merge commit is being completed, use
git commit --no-edit so a non-interactive runner does not open an editor;
normal hooks still run. Confirm the merge commit exists and MERGE_HEAD is
gone before reporting success.
If a formatter changes files and prevents the commit, review and re-stage
those files, then create a new commit attempt; do not use --amend.
A failed hook can leave all intended changes staged. Keep the staged changes,
fix the reported lint issue, rerun the affected test, and retry the normal
commit; do not use --no-verify or amend to bypass the failure.
When editing harness files such as AGENTS.md, CLAUDE.md, or skills, run
the shared validation in
.agents/skills/harness-improvement/references/validation.md before
committing.
If full-repository spec lint fails on a pre-existing duplicate acceptance ID
while the active hook requires a clean result, confirm it is baseline-only,
make the smallest unique-ID correction needed for that hook, rerun the full
lint, and record the baseline correction in the implementation record. Do
not bypass the hook or leave the repository-wide failure unexplained.
If a JSX layout-only edit touches an element containing an existing
hardcoded user-facing literal, i18n-new-code may classify that literal as
changed copy and fail. Localize it and add matching en/pseudo catalog
entries before retrying; verify with cd apps/web && pnpm run i18n:check and
the normal hook receipt.
If the commit command returns an exec_command session_id, preserve the
full result and poll that same session until it is terminal; never start a
second commit attempt while the first is live. Retain the exact temporary
log path from that single attempt for the hook receipt. Capture the normal
hook stream in a temporary log while committing and use that log to record
each hook ID and result. Run git commit directly and preserve its full
output; do not infer hook results from a condensed launcher summary. For example:
COMMIT_LOG="$(mktemp "${TMPDIR:-/tmp}/kandev-commit.XXXXXX.log")"
set -o pipefail
git commit -m "type(scope): description" 2>&1 | tee "$COMMIT_LOG" >/dev/null
Keep the commit and tee output unmodified when parsing the log. Read it
to extract every hook ID/result and confirm the receipt still says
bypass: false, rather than printing the full stream again. Remove the
exact temporary file after copying the receipt into the handoff:
unlink "$COMMIT_LOG"
If a timed-out hook leaves a commit or linter process running, inspect
git status, git log, and only that attempt's processes before retrying.
Wait for or stop the owned process tree first; do not mistake a transient
parallel golangci-lint is running message for a second commit failure.
If the commit exits nonzero, preserve and print the full or bounded log
before cleanup so hook diagnostics are not lost. Remove the temporary log
only after copying a successful hook receipt or recording the failed output.
When extracting receipt lines from a nested shell, use shell-safe single-
quoted awk or sed expressions; a double-quoted sed range containing
$ can be expanded by the outer shell and hide the receipt. Record active
hooks that report Skipped or no files to check as skipped; do not infer
their result from the launcher's summary, and retain the log until both the
pre-commit and commit-msg results are recorded.
If a hook fails only because another worktree is already running
golangci-lint (for example, parallel golangci-lint is running), wait for
that run to finish and retry the same commit. Do not bypass hooks or change
code for this transient lock; verify the retry has a normal hook receipt and
a clean worktree.
If a hook fails with ENOSPC, load /verify's Disk-constrained runners
guidance: inspect df, preserve managed caches, relocate only the affected
cache to an explicit persistent agent-owned path, and never bypass hooks or
blindly delete shared caches.
Return a hook receipt: After a successful commit, report:
parent_sha: <pre-commit HEAD>
commit_sha: <new HEAD>
pre_commit_hook: active|inactive
commit_msg_hook: active|inactive
hook_results: <hook-id=passed|skipped, ...>
bypass: false|true
commit_result: pass
worktree: clean|dirty
verify may use the receipt only when both hooks are active, bypass is
false, the commit succeeded, the current HEAD still equals commit_sha,
and the worktree is clean. The commit worker does not run verification.