
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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- 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
Dans quel contexte utiliser le mode headless ?
Section intitulée « Dans quel contexte utiliser le mode headless ? »- 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é.
Prérequis
Section intitulée « Prérequis »claude --versionfonctionnel dans le terminallab-claudeopérationnel,ruffetpytestverts- Compréhension des permissions
settings.json - Un compte GitHub si vous souhaitez dérouler la partie Actions
Lancement de base : claude -p
Section intitulée « Lancement de base : claude -p »La forme minimale lance Claude avec un prompt et imprime la réponse sur stdout.
claude -p "Résume le dépôt courant en 5 points. Ne modifie aucun fichier."Quelques variantes immédiatement utiles :
# Piper un fichier comme contextecat README.md | claude -p "Relève les 3 incohérences majeures avec le code actuel."
# Piper un diff gitgit diff main...HEAD | claude -p "Relis ce diff. Liste les remarques bloquantes en 3 puces max."
# Sortie JSON pour un scriptclaude -p --output-format json "Donne la liste des endpoints de app/main.py"Formats de sortie
Section intitulée « Formats de sortie »| Option | Sortie | Bon pour |
|---|---|---|
| rien (défaut) | Texte brut | Lecture humaine, affichage dans un terminal |
--output-format json | JSON structuré (role, content, usage, etc.) | Script qui parse la réponse avec jq |
--output-format stream-json | Lignes JSON au fur et à mesure | Pipeline qui consomme la sortie en flux |
Exemple de parsing JSON :
claude -p --output-format json "Liste les endpoints de app/main.py" \ | jq -r '.content'Gérer les permissions sans confirmation possible
Section intitulée « Gérer les permissions sans confirmation possible »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égie | Flag / réglage | Quand l’utiliser |
|---|---|---|
| Tout refuser par défaut | settings.json avec allow explicite et riche | CI qui exécute un périmètre connu |
| Pré-accepter les edits | --permission-mode acceptEdits | Script qui doit écrire, avec impact limité |
| Aucune permission | --permission-mode plan | Revue 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.
Authentification en environnement non-interactif
Section intitulée « Authentification en environnement non-interactif »En session interactive, Claude Code utilise votre connexion OAuth. En headless, vous avez deux chemins :
| Contexte | Mécanisme |
|---|---|
| Machine locale déjà connectée | Rien à faire, la session stockée est utilisée |
| CI, conteneur, machine neuve | Variable 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é.
Application sur lab-claude — script local
Section intitulée « Application sur lab-claude — script local »Objectif : un script shell que vous lancez avant git push et qui fait relire votre diff par Claude.
-
Placez-vous à la racine
Fenêtre de terminal cd ~/Projets/lab-claude -
Créez
scripts/review.shmkdir -p scriptscat > scripts/review.sh <<'EOF'#!/usr/bin/env bashset -euo pipefailDIFF=$(git diff main...HEAD)if [ -z "$DIFF" ]; thenecho "Aucun diff par rapport à main."exit 0fiecho "$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."EOFchmod +x scripts/review.sh -
Lancez-le
Fenêtre de terminal ./scripts/review.sh -
Intégrez-le à votre routine
Ajoutez un alias local, ou appelez-le depuis un hook
pre-pushgit. Pas besoin d’un.huskyou d’une dépendance npm, un simple.git/hooks/pre-pushsuffit.
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.
-
Ajoutez la clé API en secret GitHub
Dans le repo GitHub :
Settings→Secrets and variables→Actions→New repository secret→ nomANTHROPIC_API_KEY, valeur depuis votre console Anthropic. -
Posez un
settings.jsonCI minimalCréez
.claude/settings.jsoncô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
denysurWriteetEditgarantit qu’en CI, Claude ne peut jamais écrire, même par accident. -
Ajoutez le workflow
.github/workflows/claude-review.ymlname: Claude review on PRon:pull_request:types: [opened, synchronize]permissions:contents: readpull-requests: writejobs:review:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4with:fetch-depth: 0- name: Install Claude Code CLIrun: curl -fsSL https://claude.ai/install.sh | bash- name: Run Claude reviewenv: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 commentuses: actions/github-script@v7with: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});} -
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
OKet le job ne poste rien.
Options fréquentes à connaître
Section intitulée « Options fréquentes à connaître »| Flag | Effet |
|---|---|
--permission-mode <mode> | default, acceptEdits, plan, ou bypassPermissions (ce dernier à éviter) |
--output-format <format> | text (défaut), json, stream-json |
--continue | Reprend 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 |
Coûts et idempotence
Section intitulée « Coûts et idempotence »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é :
haikupour une revue légère,sonnetpour une analyse sérieuse - Caper avec
--max-turnssi 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.
Erreurs fréquentes
Section intitulée « Erreurs fréquentes »| Symptôme | Cause probable | Correction |
|---|---|---|
claude: command not found en CI | CLI non installé dans l’image | Ajouter l’étape curl ... install.sh ou utiliser une image qui l’inclut |
| Le job CI reste bloqué | Prompt interactif déclenché faute de permissions | Ajouter --permission-mode plan ou un allow complet |
| Sortie JSON illisible | Prompt renvoie du Markdown que jq gère mal | Extraire le bon champ (.content) et nettoyer avec sed si besoin |
| Claude écrit un fichier en CI | Write/Edit laissés implicitement autorisés | Ajouter Write(./**) et Edit(./**) dans deny |
| Coût qui dérape | Contexte trop gros ou prompt trop ouvert | Piper moins, borner le format, basculer sur haiku |
ANTHROPIC_API_KEY n’est pas vue | Secret mal câblé dans le workflow | Vérifier la section env: du step et le nom du secret |
Checklist de fin de guide
Section intitulée « Checklist de fin de guide »-
claude -p "..."fonctionne sur ma machine locale - J’ai un script
scripts/review.shou équivalent pour mon usage quotidien - Mon
settings.jsonCI a undenysurWriteetEdit - Mon job CI utilise
ANTHROPIC_API_KEYcomme 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
À retenir
Section intitulée « À retenir »claude -ptransforme Claude en commande shell comme une autre- En CI, préférez un
settings.jsonrestrictif à--permission-mode bypassPermissions --output-format jsonrend la sortie exploitable parjqet 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