
Le mode headless (claude -p) exécute Claude Code sans session interactive : un prompt en argument, une réponse sur stdout, un code de sortie exploitable par un script. Cette page pose les bases CLI, montre comment obtenir une sortie typée plutôt qu'un texte à deviner, et déroule une intégration GitHub Actions qui relit chaque PR ouverte sur lab-claude. Elle insiste surtout sur un point que la plupart des tutoriels passent sous silence : sans l'option --bare, un claude -p charge les hooks, les serveurs MCP et le CLAUDE.md du dépôt courant, sans jamais vous demander votre confiance. C'est la première chose à régler avant de brancher Claude sur une chaîne d'intégration.
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, JSON ou typée par un schéma
- Isoler l'exécution avec
--barepour un résultat identique partout - Gérer les permissions dans un environnement sans confirmation possible
- Déclencher un job GitHub Actions qui fait relire chaque PR par Claude
- Choisir entre l'intégration officielle et un appel
claude -pmaison - 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 "Donne la liste des endpoints de app/main.py" --output-format jsonFormats 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é : result porte le texte, avec session_id, l'usage et total_cost_usd | 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. Le texte de la réponse se trouve dans .result,
et non dans .content : c'est l'erreur la plus fréquente sur ce format, et elle
donne un null silencieux qui casse le script plus loin.
claude -p "Liste les endpoints de app/main.py" \ --output-format json |jq -r '.result'La charge JSON porte aussi session_id, l'usage et total_cost_usd, ce qui
permet de suivre le coût par invocation sans consulter de tableau de bord.
Demander une sortie typée plutôt que de la deviner
Section intitulée « Demander une sortie typée plutôt que de la deviner »En intégration continue, ne demandez pas à jq de deviner la structure d'une
réponse en langage naturel. L'option --json-schema impose un schéma JSON
à la réponse, qui arrive alors dans le champ structured_output. C'est la
différence entre un script qui casse au premier changement de formulation et un
script qui contractualise ce qu'il reçoit.
claude -p "Extrais les endpoints de app/main.py" \ --output-format json \ --json-schema '{"type":"object","properties":{"endpoints":{"type":"array","items":{"type":"string"}}},"required":["endpoints"]}' \ | jq '.structured_output'Si le schéma est invalide, claude s'arrête sur Error: --json-schema is not a valid JSON Schema suivi du diagnostic du validateur. Le mot-clé format, par
exemple "format": "email", est accepté mais traité comme une annotation,
donc non contraignant : ne comptez pas dessus pour valider une donnée.
Pourquoi --bare est indispensable en intégration continue
Section intitulée « Pourquoi --bare est indispensable en intégration continue »Sans --bare, un claude -p charge le même contexte qu'une session
interactive. C'est le point de sécurité le plus important de ce guide, et il
surprend presque tout le monde. Concrètement, -p découvre et charge
automatiquement les hooks, les skills, les commandes
personnalisées, les subagents, les plugins, les serveurs MCP, la
mémoire automatique et le CLAUDE.md trouvés dans le répertoire de
travail ou dans ~/.claude.
Et il le fait sans rien vous demander. La documentation est explicite : un
-p exécute les hooks du .claude/settings.json d'un projet et se connecte aux
serveurs de son .mcp.json même dans un dossier auquel vous n'avez jamais
accordé votre confiance, parce qu'une session -p n'affiche ni le dialogue
de confiance de l'espace de travail, ni la demande d'approbation par serveur.
Posez le raisonnement sur une chaîne d'intégration qui construit une pull
request venue de l'extérieur : le dépôt cloné apporte ses propres hooks et sa
propre configuration MCP, et votre claude -p les exécute. C'est une frontière
de confiance franchie sans intention.
claude --bare -p "Résume README.md" --allowedTools "Read"En mode bare, Claude Code ne lit ni les identifiants OAuth, ni le trousseau du
système. Votre abonnement ne s'applique donc pas : pour l'API Anthropic, il
faut exporter ANTHROPIC_API_KEY, ou fournir un apiKeyHelper dans le JSON
passé à --settings. Amazon Bedrock, Google Cloud et Microsoft Foundry
continuent de lire leurs propres identifiants.
Le mode bare ne vous prive de rien : il vous oblige à déclarer ce que vous chargez, ce qui est exactement ce qu'on attend d'une chaîne reproductible.
| Pour charger | Passer |
|---|---|
| des ajouts au prompt système | --append-system-prompt, --append-system-prompt-file |
| des réglages | --settings <fichier-ou-json> |
| des serveurs MCP | --mcp-config <fichier-ou-json> |
| des agents personnalisés | --agents <json> |
| un plugin | --plugin-dir <chemin>, --plugin-url <url> |
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 une demande de permission. Le
mode de démarrage d'un -p est Manual sur tous les plans : si vous ne
choisissez rien, la première demande bloque le job. Quatre stratégies, de la
plus stricte à la plus permissive :
| Stratégie | Réglage | Quand l'utiliser |
|---|---|---|
| Refuser tout ce qui n'est pas listé | --permission-mode dontAsk | exécution verrouillée : c'est le mode que la documentation recommande pour la CI |
| Lister explicitement | settings.json avec un allow précis, ou --allowedTools | pipeline dont le périmètre d'outils est connu |
| Explorer sans modifier les sources | --permission-mode plan | revue de diff, analyse. Attention, plan exécute des commandes en lecture seule : ce n'est pas une isolation |
| Pré-accepter les écritures | --permission-mode acceptEdits | script qui doit écrire, avec un impact borné |
Quand personne ne peut répondre, ajoutez --permission-prompts none : tout
ce qui déclencherait une demande est refusé, Claude en est informé et
n'insiste pas, et le job continue au lieu d'attendre. L'option demande Claude
Code 2.1.259 ou plus récent.
Règle générale : en CI, préférez un périmètre listé explicitement à 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, sans --bare | rien à faire, la session stockée est utilisée |
Avec --bare | ANTHROPIC_API_KEY obligatoire : le mode bare ne lit ni les identifiants OAuth ni le trousseau du système |
| CI, conteneur, machine neuve | ANTHROPIC_API_KEY, clé créée dans la console Anthropic |
Le piège tient en une phrase : le jour où vous ajoutez --bare à un script
qui marchait sur votre poste, il cesse de fonctionner faute de clé. Ce n'est pas
une régression, c'est la contrepartie de l'isolation. Amazon Bedrock, Google
Cloud et Microsoft Foundry continuent, eux, de lire leurs propres identifiants.
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 --bare -p \--permission-mode dontAsk \"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": ["Write(./**)","Edit(./**)"]}}Ce qui protège réellement ici, c'est la liste
allow, pas la listedeny. LedenysurWriteetEditporte sur des noms d'outils : il les retire, et c'est solide. En revanche, une version antérieure de cette page ajoutaitBash(curl *)etBash(wget *)audeny, ce qui donne une fausse assurance : une règle Bash s'applique au texte de la commande, et/usr/bin/curloush -c 'curl …'y échappent. C'est le contre-exemple que donne la documentation elle-même.La protection vient donc du fait que seules les commandes
git diff,git logetgit statussont autorisées : tout le reste,curlcompris, n'est jamais approuvé. Pour une contrainte réseau qui ne dépende pas de l'écriture de la commande, il faut un sandbox ou un hookPreToolUse, traités dans les leçons de sécurité du parcours. -
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1with:fetch-depth: 0- name: Install Claude Code CLIrun: |curl -fsSL https://claude.ai/install.sh -o claude-install.shbash claude-install.sh- 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 --bare -p \--permission-mode dontAsk \--permission-prompts none \--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 '.result' > review.md- name: Post commentuses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0with: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.
Faut-il vraiment écrire son propre workflow ?
Section intitulée « Faut-il vraiment écrire son propre workflow ? »Vous venez de construire l'intégration à la main, et c'était l'objectif : comprendre ce qui se passe. En production, la question se pose autrement, car Anthropic publie des intégrations officielles qui font ce travail pour vous.
L'échelle de décision tient en trois lignes :
| Votre situation | Ce qu'il faut employer |
|---|---|
| Dépôt GitHub, besoin courant : répondre à une mention, relire une PR, transformer une issue en PR | l'action officielle anthropics/claude-code-action |
| Dépôt GitLab | l'intégration GitLab CI/CD officielle |
| Workflow spécifique : votre propre orchestration, un cron maison, un outil interne | claude --bare -p, ou l'Agent SDK en Python ou TypeScript |
Ne reconstruisez pas ce qui existe. Le cas le plus fréquent, faire relire les pull requests, est couvert par un produit maintenu, et le maintenir vous-même vous fait porter des correctifs que quelqu'un d'autre écrit déjà.
L'installation en une commande
Section intitulée « L'installation en une commande »Depuis une session Claude Code ouverte dans le dépôt :
/install-github-appLa commande installe l'application GitHub, crée le secret d'authentification et
pousse une branche avec les fichiers de workflow, prête à ouvrir en pull
request. Elle exige un accès administrateur au dépôt et le CLI GitHub
authentifié par gh auth login.
Le workflow minimal, et ce qui n'y est pas du décor
Section intitulée « Le workflow minimal, et ce qui n'y est pas du décor »name: Claude Code
on: issue_comment: types: [created] pull_request_review_comment: types: [created]
jobs: claude: if: contains(github.event.comment.body, '@claude') runs-on: ubuntu-latest permissions: contents: write pull-requests: write issues: write id-token: write actions: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 1
- uses: anthropics/claude-code-action@19dda84776b3518d98b8798e591daee763049ed3 # v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}Quatre lignes méritent une explication, parce qu'on les supprime facilement en croyant simplifier :
id-token: writeest requis pour l'authentification par défaut via l'application GitHub. Sans elle, rien ne démarre.actions: readpermet à Claude de lire les résultats de CI sur les pull requests, ce qui change beaucoup la qualité de ses réponses.actions/checkoutdonne une copie locale du dépôt.- Le
if:évite de démarrer un runner sur chaque commentaire. L'action vérifie de son côté la phrase de déclenchement, mais autant ne pas payer le démarrage.
Deux modes, détectés automatiquement
Section intitulée « Deux modes, détectés automatiquement »L'action déduit son mode de votre configuration, sans réglage explicite.
| Mode | Déclenchement | Où arrive le résultat |
|---|---|---|
| Interactif | pas d'entrée prompt : Claude attend la phrase @claude dans un commentaire, une revue, ou le corps d'une issue | en commentaire sur l'issue ou la PR |
| Automatisation | une entrée prompt est fournie : Claude s'exécute sans attendre de mention | dans le journal du job, sauf si le prompt lui demande de publier |
Qui a le droit de déclencher une exécution
Section intitulée « Qui a le droit de déclencher une exécution »Deux vérifications précèdent chaque exécution, et c'est une bonne nouvelle
pour votre modèle de menace. L'auteur du déclenchement doit avoir l'accès en
écriture au dépôt, sur les événements d'issue et de pull request. Et un
acteur robot est refusé, sauf s'il figure dans allowed_bots, ce qui
empêche deux automatisations de se déclencher mutuellement en boucle.
Cette seconde vérification s'applique aussi aux exécutions planifiées, que
GitHub attribue à un utilisateur du dépôt, en général la dernière personne à
avoir modifié le cron.
Authentification : clé API ou jeton d'abonnement
Section intitulée « Authentification : clé API ou jeton d'abonnement »Deux secrets possibles, et le choix n'est pas indifférent.
| Secret | Origine | Quand le préférer |
|---|---|---|
ANTHROPIC_API_KEY | console Anthropic | à l'échelle d'une organisation |
CLAUDE_CODE_OAUTH_TOKEN | claude setup-token en local | usage individuel, sur un abonnement Pro, Max, Team ou Enterprise |
Pour un secret partagé entre dépôts, prenez la clé API. Un jeton OAuth est
lié à l'abonnement de la personne qui a lancé claude setup-token : le jour
où elle quitte l'équipe, toute l'organisation s'arrête.
Pour ne stocker aucun secret durable, la fédération d'identité échange
le jeton OIDC du workflow contre un accès à l'API, via un compte de service.
Elle demande la permission id-token: write et trois entrées :
anthropic_federation_rule_id, anthropic_organization_id et
anthropic_service_account_id.
Options fréquentes à connaître
Section intitulée « Options fréquentes à connaître »| Flag | Effet |
|---|---|
--bare | n'auto-découvre ni hooks, ni skills, ni commandes, ni subagents, ni plugins, ni MCP, ni mémoire, ni CLAUDE.md. À mettre partout en CI |
--permission-mode <mode> | default, acceptEdits, plan, auto, dontAsk, bypassPermissions. En CI, dontAsk |
--permission-prompts none | refuse au lieu d'attendre quand personne ne peut répondre |
--json-schema <schema> | impose un schéma à la réponse, récupérée dans structured_output |
--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 par son alias (sonnet, opus, haiku, fable). L'alias suit le modèle recommandé, ce qui évite d'épingler un identifiant qui vieillit |
--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é | une demande de permission attend une réponse que personne ne donnera | --permission-mode dontAsk et --permission-prompts none, plus un allow qui couvre le périmètre réel |
| Un hook ou un serveur MCP inattendu s'exécute en CI | -p charge le contexte du dépôt cloné sans dialogue de confiance | ajouter --bare, puis déclarer explicitement ce dont le job a besoin |
jq renvoie null sur la sortie JSON | mauvais champ : le texte est dans .result | utiliser jq -r '.result', ou --json-schema puis jq '.structured_output' |
| 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 - Tous mes appels scriptés passent
--bare, et je sais ce que je recharge explicitement - Je parse
.result, oustructured_outputquand j'ai imposé un schéma - Je sais qu'un
denysur une commande Bash ne remplace pas un sandbox - Je sais reconnaître les cas où l'intégration officielle vaut mieux que mon propre workflow
- J'ai borné mon prompt (format court) pour contrôler les coûts
- Je sais que Claude ne remplace pas
ruff+pytest
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
À 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
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Claude Code dans VS Code : Le retour à l'interactif riche pour instruire ce qu'un job CI a signalé.
- Erreurs courantes et recadrage en session : Les dérives typiques à reconnaître avant de les laisser tourner sans surveillance.
- Dépannage avancé : permissions, config, diff : Le diagnostic d'un job qui échoue sur une permission ou une configuration ignorée.