Aller au contenu
Développement medium

Claude Code : mode headless et intégration CI pour automatiser sans session interactive

12 min de lecture

Logo Claude Code - mode headless et CI

Jusqu’ici Claude Code vous a servi en session interactive. Dès que vous voulez l’exécuter dans un script local, un pré-commit, un job CI ou un cron, la boucle « attendre la saisie utilisateur » n’a plus de sens. Le mode headless (claude -p) répond à ce besoin : un prompt en argument, une réponse sur stdout, un code de sortie exploitable. Ce guide pose les bases CLI et déroule une intégration GitHub Actions concrète qui relit chaque PR ouverte sur lab-claude.

  • Lancer Claude en non-interactif avec claude -p "prompt"
  • Piper du contexte via stdin (diff, log, fichier)
  • Récupérer une sortie texte ou JSON exploitable par un script
  • Gérer les permissions dans un environnement sans confirmation possible
  • Déclencher un job GitHub Actions qui fait relire chaque PR par Claude
  • Comprendre les limites : coûts, idempotence, secrets
  • Automatiser une revue légère de diff à chaque PR ou pré-commit
  • Générer un changelog ou une synthèse depuis un historique git
  • Valider un artefact (doc, config) dans un pipeline
  • Piloter Claude depuis un outil maison (script Python, job Airflow, webhook)

Ce que ce n’est pas : un remplacement à vos tests. Claude produit une analyse probabiliste, pas une vérité déterministe. Gardez ruff + pytest comme filet de sécurité.

  • claude --version fonctionnel dans le terminal
  • lab-claude opérationnel, ruff et pytest verts
  • Compréhension des permissions settings.json
  • Un compte GitHub si vous souhaitez dérouler la partie Actions

La forme minimale lance Claude avec un prompt et imprime la réponse sur stdout.

Fenêtre de terminal
claude -p "Résume le dépôt courant en 5 points. Ne modifie aucun fichier."

Quelques variantes immédiatement utiles :

Fenêtre de terminal
# Piper un fichier comme contexte
cat README.md | claude -p "Relève les 3 incohérences majeures avec le code actuel."
# Piper un diff git
git diff main...HEAD | claude -p "Relis ce diff. Liste les remarques bloquantes en 3 puces max."
# Sortie JSON pour un script
claude -p --output-format json "Donne la liste des endpoints de app/main.py"
OptionSortieBon pour
rien (défaut)Texte brutLecture humaine, affichage dans un terminal
--output-format jsonJSON structuré (role, content, usage, etc.)Script qui parse la réponse avec jq
--output-format stream-jsonLignes JSON au fur et à mesurePipeline qui consomme la sortie en flux

Exemple de parsing JSON :

Fenêtre de terminal
claude -p --output-format json "Liste les endpoints de app/main.py" \
| jq -r '.content'

En headless, personne n’est là pour approuver un prompt de permission. Trois stratégies s’offrent à vous, de la plus stricte à la plus permissive :

StratégieFlag / réglageQuand l’utiliser
Tout refuser par défautsettings.json avec allow explicite et richeCI qui exécute un périmètre connu
Pré-accepter les edits--permission-mode acceptEditsScript qui doit écrire, avec impact limité
Aucune permission--permission-mode planRevue pure, lecture seule

Règle : en CI, préférez un settings.json projet qui liste exactement ce que Claude peut faire, plutôt qu’un mode global permissif.

En session interactive, Claude Code utilise votre connexion OAuth. En headless, vous avez deux chemins :

ContexteMécanisme
Machine locale déjà connectéeRien à faire, la session stockée est utilisée
CI, conteneur, machine neuveVariable d’environnement ANTHROPIC_API_KEY (clé API depuis la console Anthropic)

En CI, la clé API est toujours un secret du job, jamais en clair dans le workflow ou dans un fichier commité.

Objectif : un script shell que vous lancez avant git push et qui fait relire votre diff par Claude.

  1. Placez-vous à la racine

    Fenêtre de terminal
    cd ~/Projets/lab-claude
  2. Créez scripts/review.sh

    mkdir -p scripts
    cat > scripts/review.sh <<'EOF'
    #!/usr/bin/env bash
    set -euo pipefail
    DIFF=$(git diff main...HEAD)
    if [ -z "$DIFF" ]; then
    echo "Aucun diff par rapport à main."
    exit 0
    fi
    echo "$DIFF" | claude -p \
    "Relis ce diff. Liste uniquement les remarques bloquantes \
    (bug probable, test manquant critique, faille). \
    3 puces max. Ne retourne rien si tout est propre."
    EOF
    chmod +x scripts/review.sh
  3. Lancez-le

    Fenêtre de terminal
    ./scripts/review.sh
  4. Intégrez-le à votre routine

    Ajoutez un alias local, ou appelez-le depuis un hook pre-push git. Pas besoin d’un .husky ou d’une dépendance npm, un simple .git/hooks/pre-push suffit.

Application CI : GitHub Actions qui relit chaque PR

Section intitulée « Application CI : GitHub Actions qui relit chaque PR »

Objectif : à chaque PR ouverte sur lab-claude, Claude relit le diff et poste un commentaire récapitulatif.

  1. Ajoutez la clé API en secret GitHub

    Dans le repo GitHub : SettingsSecrets and variablesActionsNew repository secret → nom ANTHROPIC_API_KEY, valeur depuis votre console Anthropic.

  2. Posez un settings.json CI minimal

    Créez .claude/settings.json côté dépôt (s’il n’existe pas déjà) :

    {
    "$schema": "https://json.schemastore.org/claude-code-settings.json",
    "permissions": {
    "allow": [
    "Bash(git diff:*)",
    "Bash(git log:*)",
    "Bash(git status)",
    "Read(./*)",
    "Read(./**)"
    ],
    "deny": [
    "Bash(rm -rf:*)",
    "Bash(curl:*)",
    "Bash(wget:*)",
    "Write(./**)",
    "Edit(./**)"
    ]
    }
    }

    Le bloc deny sur Write et Edit garantit qu’en CI, Claude ne peut jamais écrire, même par accident.

  3. Ajoutez le workflow .github/workflows/claude-review.yml

    name: Claude review on PR
    on:
    pull_request:
    types: [opened, synchronize]
    permissions:
    contents: read
    pull-requests: write
    jobs:
    review:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    with:
    fetch-depth: 0
    - name: Install Claude Code CLI
    run: curl -fsSL https://claude.ai/install.sh | bash
    - name: Run Claude review
    env:
    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
    run: |
    DIFF=$(git diff origin/${{ github.event.pull_request.base.ref }}...HEAD)
    echo "$DIFF" | claude -p \
    --permission-mode plan \
    --output-format json \
    "Relis ce diff. Liste les remarques bloquantes, à discuter, mineures. \
    Sois bref (max 8 lignes au total). Si rien à dire, réponds exactement: OK." \
    | jq -r '.content' > review.md
    - name: Post comment
    uses: actions/github-script@v7
    with:
    script: |
    const fs = require('fs');
    const body = fs.readFileSync('review.md', 'utf8').trim();
    if (body && body !== 'OK') {
    github.rest.issues.createComment({
    owner: context.repo.owner,
    repo: context.repo.repo,
    issue_number: context.payload.pull_request.number,
    body: '### Revue Claude\n\n' + body
    });
    }
  4. Poussez une branche et ouvrez une PR

    Le job se déclenche, Claude lit le diff, et le commentaire apparaît dans la PR. Si le diff est jugé propre, Claude répond OK et le job ne poste rien.

FlagEffet
--permission-mode <mode>default, acceptEdits, plan, ou bypassPermissions (ce dernier à éviter)
--output-format <format>text (défaut), json, stream-json
--continueReprend la dernière session (moins utile en CI, utile en script local)
--model <alias>Force un modèle (sonnet, haiku, opus). Utile pour borner les coûts
--max-turns <N>Limite le nombre de tours, protection anti-boucle

Un appel headless consomme des tokens à chaque invocation. Bons réflexes :

  • Limiter la taille du contexte : ne pipez pas tout le dépôt, seulement le diff ou le fichier pertinent
  • Imposer un format court dans le prompt (« 3 puces max », « max 8 lignes »)
  • Choisir un modèle proportionné : haiku pour une revue légère, sonnet pour une analyse sérieuse
  • Caper avec --max-turns si Claude peut enchaîner des outils

Un job CI qui tourne sur chaque push, avec un prompt non borné, peut devenir cher très vite. Mesurez en local avant de laisser tourner.

SymptômeCause probableCorrection
claude: command not found en CICLI non installé dans l’imageAjouter l’étape curl ... install.sh ou utiliser une image qui l’inclut
Le job CI reste bloquéPrompt interactif déclenché faute de permissionsAjouter --permission-mode plan ou un allow complet
Sortie JSON illisiblePrompt renvoie du Markdown que jq gère malExtraire le bon champ (.content) et nettoyer avec sed si besoin
Claude écrit un fichier en CIWrite/Edit laissés implicitement autorisésAjouter Write(./**) et Edit(./**) dans deny
Coût qui dérapeContexte trop gros ou prompt trop ouvertPiper moins, borner le format, basculer sur haiku
ANTHROPIC_API_KEY n’est pas vueSecret mal câblé dans le workflowVérifier la section env: du step et le nom du secret
  • claude -p "..." fonctionne sur ma machine locale
  • J’ai un script scripts/review.sh ou équivalent pour mon usage quotidien
  • Mon settings.json CI a un deny sur Write et Edit
  • Mon job CI utilise ANTHROPIC_API_KEY comme secret et pas en clair
  • J’ai borné mon prompt (format court) pour contrôler les coûts
  • Je sais que Claude ne remplace pas ruff + pytest
  • claude -p transforme Claude en commande shell comme une autre
  • En CI, préférez un settings.json restrictif à --permission-mode bypassPermissions
  • --output-format json rend la sortie exploitable par jq et autres scripts
  • Claude en CI produit de l’analyse, pas de la vérité : il complète vos tests, ne les remplace pas
  • Coûts et périmètre se contrôlent par le prompt, pas seulement par les flags

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn