Aller au contenu principal

Module 14 — Mode non interactif, CI GitHub Actions et Agent SDK

Ce module retire le clavier : Claude Code tourne dans un cron nocturne, une PR GitHub Actions et un script Python de quarante lignes. Trois surfaces, une règle — le prompt et la palette d'outils sont le contrat, le reste est configuration.

claude -p : la CLI en mode non interactif

claude -p "…" (alias --print) exécute une requête sans session interactive. headless.md et cli-reference.md fixent les drapeaux exacts ; sélection utile en CI :

DrapeauRôle
-p, --printRend la sortie et quitte. Incompatible avec --bg et avec --cloud "tâche"
--bareSaute hooks, skills, commandes, sous-agents, plugins, MCP, mémoire auto, CLAUDE.md — laisse Bash, Read, Edit. Recommandé en CI
--output-format text|json|stream-jsonTexte, JSON (result, session_id, métadonnées), ou JSON ligne par ligne
--include-partial-messagesDeltas de streaming ; exige --print --output-format stream-json --verbose
--verboseJournalisation tour par tour
--forward-subagent-textRéémet texte et pensées des sous-agents (v2.1.211+)
--json-schema '{…}'Impose un schéma JSON, remplit structured_output
--max-turns <N>Plafond de tours en print, échoue au dépassement
--continue, -c / --resume, -r <id|nom>Reprend la dernière conversation / une session précise
--no-session-persistenceNe persiste pas la session sur disque (print uniquement)
--allowedTools "Bash,Read,Edit" (alias --allowed-tools)Auto-approuve selon la syntaxe des règles de permission
--disallowedTools "…"Retire un outil ("Edit") ou refuse un motif (Bash(rm *))
--tools "…"Restreint la palette intégrée ; "" tout désactive, "default" tout remet
--permission-mode default|acceptEdits|plan|auto|dontAsk|bypassPermissionsMode initial. Sous -p, défaut Manual sur tous les plans
--permission-prompts host|noneQui répond aux demandes ; none refuse sans opérateur (v2.1.259+)
--permission-prompt-tool mcp_xDélègue les demandes à un outil MCP
--append-system-prompt "…" / --append-system-prompt-fileAjoute du texte au prompt système par défaut
--system-prompt "…" / --system-prompt-fileRemplace intégralement le prompt système
--mcp-config <file|json>Charge des serveurs MCP ; attend leur connexion jusqu'à MCP_TIMEOUT (30 s)
--add-dir <path>Ajoute un dossier à la portée de lecture/édition
--model <alias>sonnet, opus, haiku, fable ou nom complet
--effort low|medium|high|xhigh|max|ultracodeNiveau d'effort de la session
--agents '{"reviewer":{…}}'Définit des sous-agents dynamiquement en JSON

Deux gestes clés en CI : piper stdin (capé à 10 Mo) et lire le code de sortie (0 succès, non nul échec, 143 sur SIGTERM). --bare est essentiel : sans lui, -p charge hooks, MCP et CLAUDE.md du dossier courant sans dialogue de confiance. --output-format json renseigne total_cost_usd et une ventilation par modèle — utile pour suivre la dépense sans passer par /usage.

cat build-error.txt | claude --bare -p 'cause racine en une phrase' \
--output-format json --max-turns 3 --allowedTools "Read" > diag.json

Le script triage.sh : trier les issues la nuit

Kiosque reçoit des dizaines d'issues mal étiquetées par semaine. Un cron à 3 h délègue le tri à Claude, en n'utilisant que du corpus officiel : -p, --bare, --output-format json, --json-schema, --allowedTools, --permission-mode dontAsk, --max-turns.

#!/usr/bin/env bash
# scripts/triage.sh — classe les issues ouvertes
set -euo pipefail
export ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:?absent}"

for id in $(gh issue list --state open --json number --jq '.[].number'); do
body=$(gh issue view "$id" --json title,body --template '{{.title}}\n\n{{.body}}')
echo "$body" | claude --bare -p \
"classe cette issue : bug|feature|question|doublon. propose 1 à 3 labels et une priorité p1|p2|p3." \
--output-format json --max-turns 2 \
--permission-mode dontAsk --allowedTools "Read" \
--json-schema '{"type":"object","required":["kind","priority","labels"],
"properties":{"kind":{"enum":["bug","feature","question","doublon"]},
"priority":{"enum":["p1","p2","p3"]},
"labels":{"type":"array","items":{"type":"string"}}}}' \
| jq -r '.structured_output | @json' \
| xargs -I{} gh issue edit "$id" --add-label "$(echo {} | jq -r '.labels|join(",")')" \
--add-label "$(echo {} | jq -r '.kind + \"/\" + .priority')"
done

--permission-mode dontAsk refuse tout ce qui n'est pas autorisé, --max-turns 2 cadenasse le coût par issue, --json-schema garantit un objet exploitable pour gh.

GitHub Actions et @claude

github-actions.md décrit anthropics/claude-code-action@v1. Installation rapide : /install-github-app depuis Claude Code installe l'app GitHub, stocke un secret (ANTHROPIC_API_KEY ou CLAUDE_CODE_OAUTH_TOKEN obtenu par claude setup-token), et ouvre une PR avec le workflow. En manuel : installer l'app, ajouter le secret, copier examples/claude.yml.

Deux modes détectés automatiquement :

  • Interactif (pas d'input prompt) : Claude répond quand @claude est mentionné dans le corps/titre d'une nouvelle issue, un commentaire de PR/issue, ou un commentaire de revue. L'auteur doit avoir un accès write (sauf allowed_non_write_users avec github_token maison) et ne pas être un bot (sauf allowed_bots).
  • Automation (avec prompt) : tourne sur n'importe quel événement, schedule inclus. Sortie par défaut dans le log ; pour poster sur la PR, le prompt doit le demander et un outil doit savoir poster.

Pour Kiosque, .github/workflows/claude.yml répond aux mentions et fait tourner la revue :

# .github/workflows/claude.yml
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@v6
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: >
--max-turns 8 --model claude-sonnet-5
--allowedTools "Bash(make test),Bash(ruff *),Read,Edit"

Non-boilerplate : id-token: write (auth de l'app), actions: read (résultats CI), garde if: (n'allume pas un runner pour rien). claude_args accepte tout drapeau de cli-reference.md. Pour une revue automatique, un second job invoque le plugin code-review avec plugin_marketplaces, plugins et prompt: "/code-review:code-review --comment …" — sans --comment, les trouvailles restent dans les logs. Cloud provider : trois inputs mutuellement exclusifs use_bedrock: "true", use_vertex: "true", use_foundry: "true", avec OIDC. GitLab CI/CD suit un modèle proche (image: node:24-alpine3.21, install par claude.ai/install.sh, puis claude -p "${AI_FLOW_INPUT}" --permission-mode acceptEdits) ; en bêta, maintenu par GitLab.

Attention aux cascades CI : si vous passez github_token: ${{ secrets.GITHUB_TOKEN }} à l'action, les commits de Claude n'allument aucun autre workflow (blocage GitHub par défaut). Retirez la ligne pour que Claude pousse comme l'app Claude Code — ses commits déclencheront alors push et pull_request.

Ordonnancer : /loop, /goal, routines

/loop répète un prompt à intervalle dans la session ; formats bare token (30m) ou clause (every 2 hours), unités s, m, h, d. Sans prompt, Claude exécute le prompt de maintenance intégré (ou .claude/loop.md / ~/.claude/loop.md). Une tâche récurrente expire à 7 jours ; Esc interrompt une itération. scheduled-tasks.md distingue /loop, routines cloud (/schedule, claude.ai/code/routines) et tâches Desktop.

/goal <condition> : après chaque tour, Haiku par défaut évalue si la condition tient. Trois verdicts : Not yet met, Met, Impossible. Une condition efficace nomme un état mesurable, une preuve, une contrainte (4 000 caractères max). En non interactif, claude -p "/goal …" tourne jusqu'à résolution ; ajoutez --output-format stream-json --verbose, sinon rien ne s'affiche avant la fin.

Agent SDK : la même boucle, en programme

agent-sdk__overview.md pose l'équivalence : l'Agent SDK expose « les mêmes outils, la même boucle agentique, la même gestion de contexte que Claude Code », dans deux paquets — claude-agent-sdk (Python 3.10+) et @anthropic-ai/claude-agent-sdk (Node 18+), embarquant le binaire natif. Authentification par clé API (ANTHROPIC_API_KEY, ou CLAUDE_CODE_USE_BEDROCK=1, CLAUDE_CODE_USE_VERTEX=1, CLAUDE_CODE_USE_FOUNDRY=1).

Point d'entrée : query(...), itérateur asynchrone qui stream les messages — AssistantMessage, appels d'outils, ResultMessage. Options Python utiles (agent-sdk__python.md) : allowed_tools, disallowed_tools, system_prompt (chaîne libre, {"type": "preset", "preset": "claude_code", "append": "…"} ou {"type": "file", "path": "…"}), mcp_servers, permission_mode, max_turns, model, cwd, add_dirs, env, hooks. Même API en camelCase côté TypeScript. Sortie structurée : JSON Schema en argument, la réponse atterrit dans structured_output.

notes-de-version.py : 40 lignes de SDK Python

Pour Kiosque, à chaque tag, produire les notes depuis les commits. Palette réduite (Bash, Read), permission_mode: acceptEdits, max_turns pour cadenasser.

# scripts/notes_de_version.py
import asyncio
import json
from claude_agent_sdk import (
query, ClaudeAgentOptions, AssistantMessage, ResultMessage,
)

PROMPT = (
"génère les notes de version pour le tag courant. utilise `git log`, "
"extrais les commits depuis le dernier tag, regroupe en sections "
"'Fonctionnalités', 'Corrections', 'Interne'. réponds en français, "
"sans emojis. renvoie strictement le markdown final."
)

OPTIONS = ClaudeAgentOptions(
allowed_tools=["Bash", "Read"],
permission_mode="acceptEdits",
max_turns=6,
system_prompt={"type": "preset", "preset": "claude_code",
"append": "les notes doivent tenir en une page A4."},
)

async def main() -> None:
async for msg in query(prompt=PROMPT, options=OPTIONS):
if isinstance(msg, AssistantMessage):
for block in msg.content:
if hasattr(block, "text"):
print(block.text)
elif hasattr(block, "name"):
print(f"[outil] {block.name}")
elif isinstance(msg, ResultMessage):
print(f"--- fin : {msg.subtype}")

if __name__ == "__main__":
asyncio.run(main())

À exécuter avec uv run scripts/notes_de_version.py. En production, filtrez les blocs texte pour publier automatiquement dans la release GitHub.

SDK ou -p

-p gagne à chaque fois que le prompt tient dans une ligne shell et que le résultat sort en JSON exploitable par jq. Le SDK devient nécessaire pour intercepter chaque outil (callback canUseTool), gérer plusieurs sessions concurrentes dans un même processus, mélanger logique métier et appel d'outil, exposer un service HTTP qui reprend par ID.

Contexte hostile

Sans --bare, claude -p charge les hooks et les serveurs MCP de .mcp.json sans dialogue de confiance. En cron, en Actions, en pré-commit : --bare par défaut, --allowedTools explicite, --permission-prompts none.

SIGTERM ferme au code 143 sans finir le tour ; SIGINT termine le tour puis quitte ; interrupt() du SDK est l'équivalent programmable. Les hooks SessionEnd tournent avant fermeture. Un bash de fond est terminé cinq secondes après le résultat final ; un sous-agent de fond fait attendre -p, plafond à dix minutes (CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS).

En résumé

claude -p avec --bare, --output-format json et souvent --json-schema est la base de toute automatisation reproductible ; les drapeaux --permission-mode dontAsk, --permission-prompts none, --allowedTools et --max-turns cadenassent un run non surveillé. GitHub Actions offre le mode interactif (@claude) et le mode automation (prompt:) via anthropics/claude-code-action@v1claude_args accepte tout drapeau CLI. /loop répète à l'intervalle, /goal vise une condition évaluée par un modèle rapide, les routines ordonnancent hors session. L'Agent SDK Python et TypeScript expose la même boucle par query(...) avec allowed_tools, permission_mode, hooks, mcp_servers, system_prompt et sortie structurée par schéma JSON.

Module suivant : « Maîtriser les coûts, la sécurité et le déploiement en équipe ».