À 2 h du matin, une tâche planifiée se déclenche sur un VPS qui ne s'est jamais endormi. Une exécution headless claude -p traite une tâche en file d'attente dans un dépôt cloné sans rien demander à personne, puis se termine. Le matin venu, un commit vous attend, ou un rapport, ou un journal qui montre exactement où l'exécution s'est arrêtée et pourquoi. Personne n'a regardé.
C'est une configuration très différente de celle qui consiste à laisser un terminal ouvert toute la nuit en espérant que la connexion SSH tienne. Un point de défaillance fréquent dans une exécution nocturne, c'est la machine hôte : un portable se met en veille, le capot se ferme, le réseau tombe, ou une mise à jour de l'OS redémarre la machine en pleine tâche. Les échecs d'authentification, les erreurs d'API et les blocages de permissions peuvent toujours tuer la tâche, mais un hôte toujours allumé élimine le mode de défaillance le plus simple.
Ce guide couvre le mécanisme concret : les options headless livrées par chaque grande CLI d'agent de code, les deux façons de déclencher une exécution planifiée et laquelle choisir, ce dont l'hôte a besoin en dessous, et les garde-fous qui évitent qu'une exécution sans surveillance coûte ou casse plus que ce que vous voudriez avoir à expliquer ensuite.
La version courte
- Chaque grande CLI d'agent de code livre un mode non interactif documenté qui exécute une invite jusqu'au bout puis se termine. Claude Code a
claude -p, Codex CLI acodex exec, et Gemini CLI agemini -p. Ce n'est pas un contournement, c'est une fonctionnalité officielle. - Claude Code dispose aussi de sa propre planification : Routines, tâches planifiées Desktop et
/loop. Pour certains lecteurs, cela suffit vraiment, et il y a moins à maintenir qu'avec un VPS. - Cron fait très bien l'affaire pour une tâche nocturne. Un timer systemd est le meilleur choix par défaut sur une machine susceptible de redémarrer, parce que
Persistent=truerattrape une exécution que cron aurait silencieusement sautée. - La CLI elle-même est légère parce que l'inférence se fait sur l'API du fournisseur. Dimensionnez le VPS pour les commandes qu'il va exécuter (tests, builds, conteneurs, jobs en parallèle), pas pour le modèle.
- Ce sont les garde-fous (outils restreints, plafond de tours, branchement sur le code de sortie) qui rendent sûr le fait de laisser une planification tourner seule. La planification en elle-même n'est pas le mécanisme de sécurité.
Ce qu'il vous faut
Préparez ces cinq éléments avant d'écrire la moindre ligne de crontab ou le moindre fichier d'unité :
- Un VPS accessible en SSH, sous une distribution Linux basée sur systemd.
- La CLI de l'agent installée sur ce VPS : Claude Code, Codex CLI ou Gemini CLI.
- Un identifiant non interactif pour la CLI que vous choisissez. Le mode bare de Claude Code ne lit aucune connexion de compte, il lui faut donc
ANTHROPIC_API_KEYdans l'environnement, ou unapiKeyHelperdans ses réglages. Une exécution normale en mode print, Codex et Gemini peuvent aussi utiliser les identifiants de compte documentés. - Un dépôt ou un répertoire de travail sur lequel l'agent va agir.
- Un accès shell autorisé à modifier une crontab ou à écrire un fichier d'unité systemd.
Faire tourner un agent sans session attachée
Chaque grande CLI d'agent de code livre un mode non interactif conçu exactement pour cela. Claude Code prend -p, qui s'écrit aussi --print. Codex CLI prend codex exec. Gemini CLI prend -p, qui s'écrit aussi --prompt. Chacun accepte une invite, l'exécute jusqu'au bout et se termine. Pas de boucle de discussion, pas de terminal à garder ouvert, rien à rattacher.
Claude Code peut-il tourner sans session active ? Oui. En passant -p lance l'invite en mode non interactif : Claude Code l'exécute jusqu'au bout, affiche le résultat et se termine. Il n'y a pas de boucle de discussion ni rien à maintenir en vie, et cela repose sur le même Agent SDK que la CLI interactive, d'après la documentation d'Anthropic sur le mode headless.
| CLI | Option non interactive | Comportement | Sortie structurée |
|---|---|---|---|
| Claude Code | -p / --print | Exécute l'invite jusqu'au bout, affiche le résultat, se termine | --output-format réglé sur text, json ou stream-json |
| Codex CLI | codex exec | Diffuse la progression sur stderr, écrit le message final sur stdout, se termine | --json pour un flux d'événements JSONL |
| Gemini CLI | -p / --prompt | Exécute l'invite de façon non interactive, se termine | --output-format json |
Ce sont les options propres à Claude Code qui comptent le plus ici, parce que ce sont celles que vous allez réellement scripter. Deux d'entre elles permettent à une exécution d'avancer sans s'arrêter pour demander une permission que personne n'est éveillé pour accorder : --allowedTools, qui pré-autorise des outils précis, et --permission-mode, qui définit le niveau de base pour toute l'exécution. --max-turns limite le nombre de tours agentiques qu'une exécution peut prendre avant de se terminer sur une erreur.
--bare ignore les hooks, les skills, les plugins, les serveurs MCP et les instructions de projet comme CLAUDE.md, pour une exécution scriptée plus rapide et plus déterministe. Cela veut aussi dire que chaque instruction dont dépend la tâche doit se trouver dans l'invite ou dans la commande. Le mode bare ne lit pas non plus la connexion de votre compte, donc la documentation d'Anthropic recommande de définir une clé d'API dans l'environnement avant de le lancer. Claude Code rejette --bg purement et simplement lorsqu'elle est combinée à -p, et rejette --cloud de la même façon lorsque vous lui donnez une description de tâche. Il nomme le conflit et s'arrête plutôt que de faire quelque chose d'ambigu.
Un exemple d'invocation complet, adaptez l'invite et la liste d'outils à votre tâche :
claude --bare -p "Review open PRs in this repo and summarize any blockers in NOTES.md" \
--allowedTools "Bash(gh pr list *),Bash(gh pr view *),Bash(gh pr diff *),Read,Edit" \
--permission-mode dontAsk \
--max-turns 8 \
--max-budget-usd 5.00 \
--output-format json
Ajustez le budget et les motifs de commandes à la tâche ; cet exemple suppose aussi que l'authentification de GitHub CLI est déjà configurée pour le compte qui l'exécute.
Si vous installez Claude Code sur un VPS tout neuf et que vous voulez la marche à suivre pour l'authentifier sur une machine sans navigateur, c'est traité séparément dans comment authentifier Claude Code sur un serveur headless ; la version courte ci-dessus suffit pour faire fonctionner une exécution planifiée.
Le mode exec de Codex CLI, décrit dans la documentation d'OpenAI sur le mode non interactif, accepte --sandbox pour choisir une politique. read-only est la valeur par défaut, workspace-write laisse l'agent écrire dans son espace de travail, et --json transforme stdout en un flux d'événements analysable par une machine plutôt qu'en texte brut. Évitez danger-full-access pour une tâche sans surveillance, sauf si le processus est isolé et que ce risque est assumé.
Le mode headless de Gemini CLI, documenté dans la documentation headless du projet lui-même, s'active automatiquement dans un environnement sans TTY, ou explicitement avec -p. Il se termine avec un code non nul spécifique selon qu'il s'agit d'une erreur générale, d'une erreur d'entrée ou d'une limite de tours atteinte, plutôt qu'avec un seul code d'échec générique.
Où la planification doit vivre
Avant tout ce travail d'installation : le fournisseur de l'agent planifie peut-être déjà cela pour vous. Claude Code propose trois options intégrées, et l'une d'elles peut réellement convenir mieux qu'un VPS que vous gérez vous-même.
| Cloud (Routines) | Tâche planifiée Desktop | /loop | |
|---|---|---|---|
| S'exécute sur | Le cloud d'Anthropic | Votre machine | Votre machine |
| La machine doit être allumée | Non requise | Requis | Requis |
| Session ouverte requise | Non requise | Non requise | Requis |
| Intervalle minimum | 1 heure | 1 minute | 1 minute |
| Accès aux fichiers locaux | Aucun, il part d'un clone tout neuf | Accès complet | Accès complet |
La documentation d'Anthropic sur les tâches planifiées présente cela comme un véritable choix à trois branches, pas comme une hiérarchie avec le VPS au sommet. Si votre tâche n'a besoin d'aucun état local, tolère un plancher d'une heure et que vous n'utilisez que Claude Code, Routines demande moins d'entretien que ce qui suit : Anthropic l'exécute dans le cloud à partir d'un clone tout neuf pendant que votre machine est éteinte.
/loop mérite d'être connu mais ne convient pas à ce cas d'usage, car il exige une session ouverte et inactive, c'est-à-dire exactement la contrainte que vous cherchez à supprimer. La même documentation cite aussi GitHub Actions comme quatrième option, pour les équipes dont le déclencheur vit déjà dans la CI plutôt que sur une planification liée à une machine précise.
Le VPS auto-géré gagne sa place quand la tâche a besoin d'un accès complet au système de fichiers local et aux outils, quand vous voulez le même mécanisme fonctionnant à l'identique avec Claude Code, Codex CLI et Gemini CLI, ou quand l'intervalle autorisé par Routines est trop grossier. Une fonction serverless classique est en général mal adaptée ici, car elle doit restaurer des identifiants, cloner le dépôt et terminer dans les limites d'exécution de la plateforme. Un runner de CI éphémère comme GitHub Actions reste une troisième voie valable quand un checkout tout neuf à chaque exécution est acceptable. Si vous avez déjà du matériel allumé en permanence qui ne sert à rien, une machine de homelab fait aussi l'affaire ; en échange, vous dépendez de la fiabilité de votre réseau domestique et de votre accès distant plutôt que de ceux d'un hébergeur.
Cron ou un timer systemd ?
Les deux outils peuvent lancer la même commande selon le même horaire, mais ils divergent sur ce qui se passe quand la machine redémarre et sur le coût d'installation de chacun :
| cron | timer systemd | |
|---|---|---|
| Poids de la mise en place | Une ligne de crontab | Un fichier .timer et un fichier .service |
| Rattrapage des exécutions manquées | Aucun, une exécution sautée est simplement perdue | Persistent=true l'exécute dès que le système est de nouveau opérationnel |
| Journalisation | Manuelle, vous redirigez la sortie vous-même | Automatique, capturée par journald |
| Ordonnancement des dépendances | Aucune | Ordonnancement systemd complet avec After= et Requires= |
Le cron classique convient très bien pour une tâche nocturne sur une machine qui redémarre rarement. Le piège, c'est l'environnement : cron démarre avec un PATH minimal, n'entre pas dans votre dépôt à votre place, et lancera volontiers une deuxième copie pendant que la première tourne encore. Mettez le chemin du dépôt, la commande d'agent restreinte et le chargement des identifiants dans un script d'encapsulation protégé, puis utilisez flock pour empêcher les exécutions qui se chevauchent.
# /usr/local/bin/agent-nightly
#!/usr/bin/env bash
set -euo pipefail
export PATH=/usr/local/bin:/usr/bin:/bin
export ANTHROPIC_API_KEY="$(
cat "$HOME/.config/agent-nightly/anthropic_api_key"
)"
cd /srv/myrepo
exec /usr/local/bin/claude --bare -p \
"Run the nightly dependency audit and write the findings to NOTES.md" \
--allowedTools "Bash(npm audit *),Read,Edit" \
--permission-mode dontAsk \
--max-turns 8 \
--max-budget-usd 5.00 \
--output-format json
# crontab -e
0 2 * * * /usr/bin/flock -n "$HOME/.local/state/agent-runs/nightly.lock" /usr/local/bin/agent-nightly >> "$HOME/.local/state/agent-runs/nightly.log" 2>&1
Créez une fois pour toutes les répertoires d'identifiants et de journaux, puis rendez le script exécutable :
install -d -m 700 \
"$HOME/.config/agent-nightly" \
"$HOME/.local/state/agent-runs"
touch "$HOME/.config/agent-nightly/anthropic_api_key"
chmod 600 "$HOME/.config/agent-nightly/anthropic_api_key"
"${EDITOR:-nano}" \
"$HOME/.config/agent-nightly/anthropic_api_key"
sudo chmod 755 /usr/local/bin/agent-nightly
Ne collez que la clé d'API dans le fichier d'identifiants. Ne la mettez pas directement dans la crontab.
Un timer systemd demande plus de mise en place et vous apporte deux choses que cron n'a pas : la journalisation via journald sans redirection bricolée à la main, et Persistent=true. L'exemple ci-dessous suppose qu'un compte agent-runner dédié possède /srv/myrepo. Stockez la clé d'API dans un fichier d'identifiants réservé à root plutôt que de l'inscrire dans l'unité.
Selon le manuel systemd.timer, régler Persistent=true signifie que « l'unité de service est déclenchée immédiatement si elle aurait dû l'être au moins une fois pendant la période d'inactivité du timer ». Une exécution qui aurait dû démarrer pendant que votre VPS redémarrait pour une mise à jour du noyau se lance donc dès son retour, au lieu de disparaître en silence jusqu'au prochain créneau.
Créez le fichier d'identifiants réservé à root utilisé par le service :
sudo install -d -m 700 /etc/agent-nightly
sudo touch /etc/agent-nightly/anthropic_api_key
sudo chmod 600 /etc/agent-nightly/anthropic_api_key
sudoedit /etc/agent-nightly/anthropic_api_key
Ne collez que la clé d'API dans le fichier.
# /etc/systemd/system/agent-nightly.service
[Unit]
Description=Nightly scoped agent run
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=agent-runner
Group=agent-runner
WorkingDirectory=/srv/myrepo
Environment=HOME=/home/agent-runner
Environment=PATH=/usr/local/bin:/usr/bin:/bin
LoadCredential=anthropic_api_key:/etc/agent-nightly/anthropic_api_key
ExecStart=/bin/sh -c 'export ANTHROPIC_API_KEY="$(cat "$CREDENTIALS_DIRECTORY/anthropic_api_key")"; exec /usr/local/bin/claude --bare -p "Run the nightly dependency audit and write the findings to NOTES.md" --allowedTools "Bash(npm audit *),Read,Edit" --permission-mode dontAsk --max-turns 8 --max-budget-usd 5.00 --output-format json'
StandardOutput=journal
StandardError=journal
UMask=0077
# /etc/systemd/system/agent-nightly.timer
[Unit]
Description=Run agent-nightly.service at 2am daily, catching up missed runs
[Timer]
# Uses the VPS's configured local timezone
OnCalendar=*-*-* 02:00:00
Persistent=true
Unit=agent-nightly.service
[Install]
WantedBy=timers.target
Rechargez systemd, activez le timer et lancez le service une fois tout de suite, pour que les problèmes d'identifiants, de permissions et de chemins apparaissent maintenant plutôt qu'à 2 h du matin :
sudo systemctl daemon-reload
sudo systemctl enable --now agent-nightly.timer
sudo systemctl start agent-nightly.service
systemctl list-timers agent-nightly.timer
sudo journalctl \
-u agent-nightly.service \
-n 100 \
--no-pager
Persistent=true est la différence décisive : le timer se souvient d'une exécution calendaire manquée au lieu de la laisser tomber en silence.
Ce dont le VPS a réellement besoin
Voici ce qui surprend ceux qui dimensionnent cela pour la première fois : la CLI elle-même est légère parce que l'inférence se fait sur l'API du fournisseur. Mais l'agent peut quand même lancer localement des builds, des tests, des gestionnaires de paquets, des serveurs de langage et des conteneurs, donc c'est la charge du dépôt qui fixe le vrai plancher.
Prenez 1 à 2 vCPU et 2 à 4 Go de RAM avec du stockage NVMe comme point de départ pour une tâche planifiée légère. Les gros dépôts, les compilateurs, les builds Docker, les suites de tests ou les exécutions concurrentes peuvent en exiger bien davantage. Ce qui pousse à monter en gamme, c'est la commande locale la plus lourde que l'agent va lancer, pas le modèle derrière l'API. Si vous faites déjà tourner des charges Docker sur ce VPS et que vous voulez une vision plus complète du budget, dimensionner et sécuriser une machine de build détaille le même arbitrage pour une autre charge de travail sans surveillance.
Une dernière chose à prévoir : une exécution sans surveillance produit des journaux chaque nuit, qu'il y ait eu un problème ou non. Ajoutez logrotate si cron écrit dans un fichier, et vérifiez les limites de rétention de journald plutôt que de supposer que ses valeurs par défaut conviennent au disque du VPS.
Toute l'approche repose sur un hôte réveillé à 2 h du matin et qui le reste, quoi que fasse votre portable. C'est précisément le rôle d'un Linux VPS avec accès root. Rien ne l'endort, et vous ne le partagez pas avec les tâches cron de quelqu'un d'autre.
Développez sur un VPS Linux avec accès root, NVMe et la puissance AMD EPYC.
Voir les plans LinuxÉviter qu'une exécution sans surveillance parte de travers
La plus grande différence entre une exécution planifiée qui marche et une qui échoue, c'est de savoir si la tâche est assez cadrée pour aboutir sans qu'un humain réponde à une question en cours de route. Les invites ambitieuses restent bloquées à attendre une décision que personne n'est là pour prendre ; les tâches étroites et autonomes se terminent proprement.
Les deux options de permission existent pour qu'une exécution ne se bloque pas sur une demande à 2 h du matin, mais un accès Bash brut n'est pas un garde-fou étroit : il peut faire presque tout ce que le compte de service peut faire. Préférez des règles spécifiques à une commande, comme Bash(git status *), associez-les à --permission-mode dontAsk, et faites tourner le service sous un compte dédié non root. Le nombre de tours et la dépense ont chacun leur plafond : --max-turns limite la durée pendant laquelle l'agent peut errer, et --max-budget-usd plafonne ce qu'une seule exécution peut dépenser en appels d'API.
Astuce : lancez avec --output-format json et enregistrez le champ total_cost_usd de chaque appel. C'est le point d'accroche le plus propre pour suivre ce qu'une exécution planifiée coûte réellement chaque nuit, et pour recevoir une alerte quand une exécution coûte nettement plus que les autres. Cela vaut les cinq minutes de câblage, parce que c'est votre facture qui est suivie, pas une abstraction.
Les dépassements de coûts sans surveillance ne sont pas hypothétiques. Dans un billet sur Hacker News, un utilisateur a signalé une facture AWS Bedrock brute de 37 901,73 $ liée à un flux de travail quotidien d'agent de code où la mise en cache des invites n'était que partiellement efficace, laissant environ 6,47 milliards de jetons d'entrée non mis en cache. Cela s'est produit sur une autre pile, pas en mode headless de Claude Code, mais cela montre pourquoi la journalisation des coûts et un budget strict par exécution ont leur place dans la planification.
Astuce : Claude Code se termine avec le code 0 en cas de succès et un code non nul en cas d'échec. Un script d'encapsulation qui vérifie le statut de sortie peut vous envoyer une notification en cas d'échec, si bien qu'une mauvaise nuit remonte dès le lendemain matin au lieu de trois jours plus tard, quand vous y jetez un œil par hasard.
Au minimum, faites tourner chaque tâche sur une branche dédiée ou un worktree jetable, et exigez une relecture humaine avant la fusion. Les identifiants à portée limitée, l'isolation du système de fichiers et la maîtrise du rayon d'impact au niveau du serveur forment un sujet plus vaste qui mérite son propre traitement plutôt qu'un paragraphe collé à la fin d'un guide de planification.
Ce sont les garde-fous qui rendent sûr le fait de laisser la planification tourner seule : la planification en elle-même n'est pas le mécanisme de sécurité.
Quand cron ne suffit plus
Une seule invite sur un timer ne demande rien de plus que ce qui est décrit ici. Trois étapes enchaînées avec une condition, une reprise et une notification Slack, c'est autre chose.
Trois options méritent d'être connues, chacune étant un cran au-dessus pour une raison différente :
- Dagu est le pas le plus léger : des jobs autonomes définis en YAML, avec des dépendances en DAG, des reprises et une interface web pour voir ce qui s'est exécuté.
- n8n convient le mieux quand l'exécution de l'agent n'est qu'un nœud parmi plusieurs intégrations et notifications, et non tout le flux de travail.
- Kestra est le plus lourd des trois, conçu pour orchestrer des pipelines de données et d'infrastructure ; c'est la bonne réponse quand planifier l'agent fait partie d'un pipeline plus vaste plutôt que d'en être l'objectif.
Pour le lecteur qui lance une seule invite chaque nuit, les trois sont surdimensionnés, et il vaut mieux le dire franchement plutôt que de vous pousser vers une installation plus lourde que nécessaire. Si une chaîne d'étapes finit par en justifier une, Dagu, n8n, et Kestra se déploient tous en un clic, ce qui est un vrai confort au moment précis où vous vous demandez si le coût d'installation en vaut la peine.
Les frameworks d'orchestration multi-agents comme LangChain ou CrewAI relèvent d'un tout autre sujet : construire des systèmes d'agents, et non planifier une CLI qui existe déjà.
Foire aux questions
Claude Code peut-il fonctionner sans session active ?
Oui. En passant -p lance l'invite en mode non interactif : Claude Code l'exécute jusqu'au bout, affiche le résultat et se termine, sans boucle de discussion ni session à garder ouverte.
Ai-je besoin d'un VPS si Claude Code a déjà Routines ?
Pas toujours. Routines tourne dans le cloud d'Anthropic machine éteinte et part d'un clone tout neuf, mais il ne peut pas accéder aux fichiers qui n'existent que sur votre machine, et il impose un intervalle minimum d'une heure. Un VPS auto-géré gagne sa place quand la tâche a besoin de fichiers locaux, d'intervalles arbitraires ou d'un mécanisme identique pour les CLI de plusieurs fournisseurs.
Faut-il utiliser cron ou un timer systemd pour un agent planifié ?
Un timer systemd, si le VPS redémarre parfois pour maintenance. Persistent=true exécute une tâche qui aurait dû se déclencher pendant l'indisponibilité dès que le système est de retour, ce à quoi cron n'a aucun équivalent. Cron convient très bien pour une tâche nocturne sur une machine qui reste allumée.
Combien de RAM faut-il à un agent IA planifié sur un VPS ?
Partez sur 1 à 2 vCPU et 2 à 4 Go de RAM pour une tâche planifiée légère, puis dimensionnez selon la commande locale la plus lourde que l'agent va lancer. Les builds, les tests, Docker, les gros dépôts et les exécutions simultanées comptent bien plus que l'inférence distante du modèle.
Le fait de planifier un agent change-t-il sa facturation ?
La planification ne crée pas de mode de facturation à part. Claude Code -p peut utiliser des identifiants d'abonnement ou une clé d'API, mais --bare ignore la connexion de l'abonnement, il lui faut donc ANTHROPIC_API_KEY dans l'environnement, ou un apiKeyHelper dans ses réglages. Codex et Gemini suivent la méthode d'authentification que vous avez configurée pour leur CLI. Comme les tarifs et les conditions d'usage évoluent vite, vérifiez les prix actuels du fournisseur et vos propres données d'usage au moment de mettre cela en place. Pour les exécutions de Claude Code via l'API, vous pouvez aussi enregistrer le champ total_cost_usd de la sortie JSON.
Discussion
Commentaires
Connectez-vous pour participer à la discussion.