{
  "schema": "https://ai-atoms.com/schemas/hook-v1.json",
  "type": "hook",
  "id": "hook/no-verify-strip",
  "version": "1.0.1",
  "name": "No-Verify Strip",
  "description": "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.",
  "event": "PreToolUse",
  "language": "python",
  "trigger": {
    "type": "tool-name",
    "pattern": "Bash"
  },
  "blocking": false,
  "side_effects": [
    "removes --no-verify flag from git commit argv",
    "logs bypass attempt to audit pipeline"
  ],
  "authored_by": "convergent-systems-key",
  "tags": [
    "git",
    "governance",
    "hooks",
    "audit",
    "no-verify",
    "claude-code"
  ],
  "lifecycle": "stable",
  "platforms": [
    "linux",
    "macos",
    "windows"
  ],
  "platform_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'.",
  "script": "#!/usr/bin/env python3\n\"\"\"hooks/no-verify-strip.py — wrapper preHook that strips --no-verify\nfrom `git commit`. Per SPEC.md §10.3 + §10.5.2.\n\nThe git command line reaches this hook two ways: the command wrapper\nsets WRAPPED_ARGV (the argv after `git`) and passes --mode=wrapper,\nwhile the Claude PreToolUse path pipes the Bash command as JSON on\nstdin. Either way the script detects whether the user requested a\nbypass and, depending on settings.secret_scanning.allowNoVerifyBypass,\neither strip silently, warn, or pass through.\n\nDefault (allowNoVerifyBypass=false): strip silently and log to the\naudit pipeline.\nOverride (allowNoVerifyBypass=true):  the wrapper config simply\nremoves this hook from preHooks (and the one-month nag fires from\nelsewhere).\n\nSelf-check:\n  --self-check\n\"\"\"\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport os\nimport sys\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path(__file__).resolve().parent))\nimport _lib  # noqa: E402\n\n\ndef git_argv_from_wrapper() -> list[str]:\n    \"\"\"The args after `git`, as JSON in WRAPPED_ARGV (command-wrapper path).\"\"\"\n    try:\n        return json.loads(os.environ.get(\"WRAPPED_ARGV\", \"[]\"))\n    except json.JSONDecodeError:\n        return []\n\n\ndef git_argv_from_stdin() -> list[str]:\n    \"\"\"The args after `git`, parsed from a Claude PreToolUse JSON payload.\"\"\"\n    raw = sys.stdin.read()\n    if not raw.strip():\n        return []\n    try:\n        payload = json.loads(raw)\n    except json.JSONDecodeError:\n        return []\n    cmd = (\n        payload.get(\"command\")\n        or payload.get(\"tool_input\", {}).get(\"command\")\n        or \"\"\n    ) if isinstance(payload, dict) else \"\"\n    parts = cmd.split()\n    return parts[1:] if parts and parts[0] == \"git\" else []\n\n\ndef check_invocation(argv: list[str]) -> int:\n    \"\"\"argv is the args AFTER `git`. Returns 0 always (this is an\n    advisory; the actual stripping happens in the wrapper).\"\"\"\n    if not argv or argv[0] != \"commit\":\n        return 0\n    stripped = []\n    bypass_seen = False\n    for a in argv[1:]:\n        if a in (\"--no-verify\", \"-n\"):\n            bypass_seen = True\n            continue\n        stripped.append(a)\n    if bypass_seen:\n        _lib.log(\"`--no-verify` was present and is being stripped.\")\n        _lib.log(\"Per Common.md §3.6: the override format is non-negotiable; same principle here.\")\n        _lib.log(\"To allow bypass: set settings.secret_scanning.allowNoVerifyBypass=true (one-month nag fires).\")\n    return 0\n\n\ndef main(argv: list[str]) -> int:\n    parser = argparse.ArgumentParser(add_help=True)\n    parser.add_argument(\"--self-check\", action=\"store_true\")\n    parser.add_argument(\"--mode\", choices=[\"claude\", \"wrapper\"], default=None,\n                        help=\"invocation mode (set by the command wrapper)\")\n    parser.add_argument(\"rest\", nargs=argparse.REMAINDER)\n    args = parser.parse_args(argv)\n    if args.self_check:\n        return _lib.self_check_ok()\n    if args.mode == \"wrapper\":\n        return check_invocation(git_argv_from_wrapper())\n    if args.mode == \"claude\" or not sys.stdin.isatty():\n        return check_invocation(git_argv_from_stdin())\n    return check_invocation(args.rest)\n\n\nif __name__ == \"__main__\":\n    sys.exit(main(sys.argv[1:]))\n",
  "depends_on": [
    "hook/lib"
  ],
  "category": "governance"
}