SkillsHooksPromptsAgentsPersonasModelsPoliciesToolsTemplatesBundlesCategoriesStart here
← Hooks
Hk hookgovernanceadvisorystable

No-Verify Strip

PreToolUse hook that strips --no-verify from git commit commands before they execute. Default behavior: strip silently and log the bypass attempt to the audit pipeline. The bypass can be allowed per-project via allowNoVerifyBypass=true in settings, which removes this hook from the preHooks list.

id hook/no-verify-stripv1.0.1by convergent-systems-key
Event
PreToolUse
Trigger
tool-nameBash
Language
python
Side effects
  • removes --no-verify flag from git commit argv
  • logs bypass attempt to audit pipeline
Platforms
linuxmacoswindows
Notes
Logic is cross-platform. Wiring: use 'ai hooks run no-verify-strip' in settings.json — the ai binary discovers Python on each OS. Modifies git argv in-process — cross-platform. Invoked via 'ai hooks run'.
Depends on
hook/lib

Script · no-verify-strip.py

#!/usr/bin/env python3
"""hooks/no-verify-strip.py — wrapper preHook that strips --no-verify
from `git commit`. Per SPEC.md §10.3 + §10.5.2.

The git command line reaches this hook two ways: the command wrapper
sets WRAPPED_ARGV (the argv after `git`) and passes --mode=wrapper,
while the Claude PreToolUse path pipes the Bash command as JSON on
stdin. Either way the script detects whether the user requested a
bypass and, depending on settings.secret_scanning.allowNoVerifyBypass,
either strip silently, warn, or pass through.

Default (allowNoVerifyBypass=false): strip silently and log to the
audit pipeline.
Override (allowNoVerifyBypass=true):  the wrapper config simply
removes this hook from preHooks (and the one-month nag fires from
elsewhere).

Self-check:
  --self-check
"""
from __future__ import annotations

import argparse
import json
import os
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
import _lib  # noqa: E402


def git_argv_from_wrapper() -> list[str]:
    """The args after `git`, as JSON in WRAPPED_ARGV (command-wrapper path)."""
    try:
        return json.loads(os.environ.get("WRAPPED_ARGV", "[]"))
    except json.JSONDecodeError:
        return []


def git_argv_from_stdin() -> list[str]:
    """The args after `git`, parsed from a Claude PreToolUse JSON payload."""
    raw = sys.stdin.read()
    if not raw.strip():
        return []
    try:
        payload = json.loads(raw)
    except json.JSONDecodeError:
        return []
    cmd = (
        payload.get("command")
        or payload.get("tool_input", {}).get("command")
        or ""
    ) if isinstance(payload, dict) else ""
    parts = cmd.split()
    return parts[1:] if parts and parts[0] == "git" else []


def check_invocation(argv: list[str]) -> int:
    """argv is the args AFTER `git`. Returns 0 always (this is an
    advisory; the actual stripping happens in the wrapper)."""
    if not argv or argv[0] != "commit":
        return 0
    stripped = []
    bypass_seen = False
    for a in argv[1:]:
        if a in ("--no-verify", "-n"):
            bypass_seen = True
            continue
        stripped.append(a)
    if bypass_seen:
        _lib.log("`--no-verify` was present and is being stripped.")
        _lib.log("Per Common.md §3.6: the override format is non-negotiable; same principle here.")
        _lib.log("To allow bypass: set settings.secret_scanning.allowNoVerifyBypass=true (one-month nag fires).")
    return 0


def main(argv: list[str]) -> int:
    parser = argparse.ArgumentParser(add_help=True)
    parser.add_argument("--self-check", action="store_true")
    parser.add_argument("--mode", choices=["claude", "wrapper"], default=None,
                        help="invocation mode (set by the command wrapper)")
    parser.add_argument("rest", nargs=argparse.REMAINDER)
    args = parser.parse_args(argv)
    if args.self_check:
        return _lib.self_check_ok()
    if args.mode == "wrapper":
        return check_invocation(git_argv_from_wrapper())
    if args.mode == "claude" or not sys.stdin.isatty():
        return check_invocation(git_argv_from_stdin())
    return check_invocation(args.rest)


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
gitgovernancehooksauditno-verifyclaude-code
Author convergent-systems-key. Catalog data license CC-BY-4.0.