Chargement

La phase de build utilisait les hooks comme garde-fous, autorisant ou bloquant des actions sans intervention humaine (Étape 3 : Build). Un hook peut aussi demander, mettant l'action en pause jusqu'à ce que la personne qui exécute la session la confirme. Lorsque l'approbateur est quelqu'un d'autre, comme un responsable des mises en production, le hook recherche son approbation enregistrée et bloque jusqu'à ce qu'elle existe.

Cette pratique se situe dans l'Étape 5 : Deploy car la porte de mise en production est le cas le plus clair, mais les hooks ne sont pas spécifiques au déploiement : ils s'exécutent partout où Claude agit. Par exemple, les hooks peuvent bloquer les modifications des migrations et de l'infrastructure sans ticket de changement pendant l'Étape 3 : Build, et empêcher l'agent de modifier des fichiers de test pendant une tâche de correction dans l'Étape 4 : Test.

Pour commencer

  • Prérequis : Aucun.
  • Infrastructure : Une liste écrite des approbations requises par le processus de changement.

Comment l'exécuter

  1. La direction de l'ingénierie, avec la gestion du changement et la conformité, dresse la liste des portes d'approbation humaines qui doivent subsister, telles que la validation de la gestion du changement, l'autorisation de mise en production et les modifications des chemins protégés.
  2. L'ingénieur de plateforme exprime chaque porte sous forme de hook, un script qui s'exécute avant que Claude n'agisse et qui peut autoriser, demander ou bloquer.
  3. Les hooks d'équipe vont dans .claude/settings.json dans Git, et les hooks non négociables vont dans les paramètres gérés détenus par la plateforme ou l'administrateur informatique, où les ingénieurs individuels ne peuvent pas les désactiver.
  4. Un blocage doit s'expliquer lui-même, donc lorsqu'un hook arrête une action, la raison et la voie vers l'approbation apparaissent dans la sortie de Claude.

À quoi cela ressemble

Cet exemple concerne le portail client d'un assureur, déployé en dev, staging et production. Le déploiement s'effectue via des outils MCP, un serveur par environnement, et chaque serveur dispose d'un outil release.

L'entrée ci-dessous est un exemple autonome dans le .claude/settings.json du projet. Son matcher capture release sur chaque serveur. La liste args vide fait en sorte que Claude Code exécute le script directement plutôt que via un shell :

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__deploy-.*__release",
        "hooks": [
          { "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/release-gate.sh",
            "args": [] }
        ]
      }
    ]
  }
}

La porte elle-même, .claude/hooks/release-gate.sh :

bash
#!/bin/bash
# Barrière de publication. dev : autoriser. staging : demander. production : un ticket de changement approuvé pour cette version, sinon bloquer.
input=$(cat)
tool=$(jq -r '.tool_name' <<<"$input")
version=$(jq -r '.tool_input.version' <<<"$input")
ticket=$(jq -r '.tool_input.ticket // empty' <<<"$input")

decide() {   # decide <allow|ask|deny> <reason>
  jq -n --arg decision "$1" --arg reason "$2" '{hookSpecificOutput: {
    hookEventName: "PreToolUse",
    permissionDecision: $decision,
    permissionDecisionReason: $reason}}'
  exit 0
}

case "$tool" in
  mcp__deploy-dev__release)
    decide allow "dev needs no approval" ;;
  mcp__deploy-staging__release)
    [ "$GITHUB_REF" = "refs/heads/main" ] && decide allow "pipeline release from main"
    decide ask "This ships $version to staging. Approve only if you own this release." ;;
  mcp__deploy-prod__release)
    route="Open a change ticket at https://change.example.com/new for version $version, \
wait for the release manager to approve it, then call release again with that ticket number."
    [[ "$ticket" =~ ^CHG-[0-9]+$ && "$version" =~ ^[0-9a-f]{7,40}$ ]] ||
      decide deny "Production needs an approved change ticket. $route"
    if approver=$(/usr/local/bin/change-ticket show --ticket "$ticket" --format json |
        jq -er --arg v "$version" 'select(.state == "approved" and .version == $v) | .approver' 2>/dev/null); then
      decide allow "$ticket approved by $approver"
    fi
    decide deny "$ticket is not approved for version $version. $route" ;;
  *)
    decide deny "No release rule for $tool. Add one to release-gate.sh first." ;;
esac

Dans le script, version et ticket sont les entrées que Claude transmet à l'outil release. change-ticket représente l'outil en ligne de commande propre à votre système de gestion du changement. La porte dépend du fait que l'agent n'a aucun moyen d'écrire dans les enregistrements qu'il lit.

Dans une exécution non interactive, personne ne peut répondre à un ask, donc Claude Code refuse l'appel. C'est pourquoi la porte autorise le staging depuis le pipeline sur main, où la fusion approuvée constitue l'approbation.

La porte a une limite. Un ingénieur pourrait définir GITHUB_REF manuellement dans sa propre session, bien que cela ne contourne que l'invite à laquelle il aurait lui-même répondu.

La raison d'un ask est affichée à la personne. La raison d'un deny est affichée à Claude, de sorte que le message de blocage indique à Claude la voie vers l'approbation à transmettre. Le résultat du staging vient d'abord, puis la production sans ticket :

json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "ask",
    "permissionDecisionReason": "This ships 4f2c9e1 to staging. Approve only if you own this release."
  }
}
json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Production needs an approved change ticket. Open a change ticket at https://change.example.com/new for version 4f2c9e1, wait for the release manager to approve it, then call release again with that ticket number."
  }
}

Considérations de gouvernance

Les hooks sont les portes d'approbation. La condition de la porte est appliquée à chaque fois, pour tout le monde. Les décisions d'autorisation et de blocage sont enregistrées avec un horodatage. La porte définit également ce qui compte comme approbation, qu'il s'agisse d'un ticket de changement approuvé ou de la validation du responsable des mises en production.

Paramètres gérés pour une entreprise réglementée

Paramètres gérés pour une entreprise réglementée, déployés par l'équipe de plateforme via la gestion des appareils mobiles (MDM) ou la console d'administration. Les ingénieurs ne peuvent ni modifier ni outrepasser aucun des paramètres qui y figurent. Voir ci-dessous :

json
{
  "permissions": {
    "deny": [
      "Read(.env*)", "Read(./secrets/**)",
      "WebFetch", "Bash(curl *)", "Bash(wget *)"
    ],
    "allow": [
      "Bash(git *)", "Bash(make build)",
      "Bash(make test)", "Bash(make lint)"
    ],
    "disableBypassPermissionsMode": "disable"
  },
  "allowManagedPermissionRulesOnly": true,
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true,
    "allowUnsandboxedCommands": false,
    "network": { "allowedDomains": ["git.internal.example.com", "registry.npmjs.org"] },
    "credentials": {
      "files": [
        { "path": "~/.ssh", "mode": "deny" },
        { "path": "~/.aws/credentials", "mode": "deny" }
      ],
      "envVars": [ { "name": "GITHUB_TOKEN", "mode": "deny" } ]
    }
  },
  "allowManagedHooksOnly": true,
  "disableSideloadFlags": true,
  "allowManagedMcpServersOnly": true,
  "strictKnownMarketplaces": [
    { "source": "github", "repo": "example-corp/approved-plugins" }
  ],
  "requiredMinimumVersion": "2.1.193"
}

Ce que font les paramètres, en termes de contrôle :

  • permissions.deny empêche les secrets d'entrer dans le contexte de l'agent et bloque toute sortie réseau arbitraire via les outils. permissions.allow préapprouve la boucle interne sûre afin que la liste de refus ne se transforme pas en fatigue des invites.
  • disableBypassPermissionsMode combiné à allowManagedPermissionRulesOnly signifie qu'aucun ingénieur, fichier de projet ou indicateur de ligne de commande ne peut élargir les règles.
  • sandbox couvre ce que les permissions ne peuvent pas couvrir. Un refus au niveau de l'outil sur WebFetch n'empêche pas une commande shell d'atteindre le réseau, alors que la liste d'autorisation de domaines au niveau du système d'exploitation bloque totalement la sortie, de sorte que les deux mécanismes appliquent un même objectif à des niveaux différents. failIfUnavailable et allowUnsandboxedCommands transforment le bac à sable en précondition, ce qui signifie que Claude Code refuse de démarrer lorsque le bac à sable ne peut pas s'initialiser, et qu'une commande qui échoue à l'intérieur du bac à sable ne peut pas être retentée à l'extérieur de celui-ci.
  • credentials traite un cas que les règles de refus ne couvrent pas. permissions.deny régit les outils de fichiers de Claude, mais une commande shell en bac à sable pourrait tout de même lire ~/.ssh ou ~/.aws/credentials par défaut. Ce bloc refuse ces lectures et retire les secrets listés de l'environnement des commandes en bac à sable.
  • allowManagedHooksOnly signifie que seuls les hooks définis dans les paramètres gérés s'exécutent ; les hooks dans les paramètres utilisateur, projet et locaux sont bloqués, y compris l'exemple autonome .claude/settings.json ci-dessus. Pour que la porte d'approbation de cette pratique reste appliquée, définissez-la dans le bloc hooks propre au fichier géré.
  • disableSideloadFlags et strictKnownMarketplaces signifient que toute compétence, agent, hook ou serveur MCP présent sur la machine d'un ingénieur provient de la place de marché de plugins approuvée par l'organisation, et non d'un répertoire personnel. La liste d'autorisation de la place de marché contrôle ce qui peut être installé, et les indicateurs qui permettraient de charger latéralement un plugin, un agent ou une configuration MCP pour une seule exécution sont rejetés au démarrage.
  • allowManagedMcpServersOnly fait de la surface d'outils de l'agent une liste d'autorisation détenue par l'équipe de plateforme.
  • requiredMinimumVersion refuse de démarrer sur une version inférieure au plancher approuvé, de sorte que les contrôles sont appliqués par une version que l'organisation a réellement évaluée.

Considérez cet exemple comme un point de départ à personnaliser selon votre propre environnement. Chaque règle de refus supprime une certaine capacité, et le bon équilibre dépend de la classification des données du dépôt. La référence des paramètres documente toutes les clés, y compris celles réservées aux paramètres gérés.

Comment le mesurer

Pour les hooks eux-mêmes :

  • Indicateur avancé : Temps passé à attendre à chaque porte d'approbation. Chaque décision de hook est écrite dans l'export OpenTelemetry avec un horodatage et un verdict d'autorisation ou de blocage, de sorte que l'attente est visible par porte.
  • Indicateur retardé : Violations de porte atteignant la production avant et après les hooks, issues du système de suivi des incidents.