8 min de lecture

Les sous-processus orphelins peuvent maintenir les identifiants actifs

Les sous-processus orphelins d'un agent peuvent maintenir des identifiants et des connexions actives après une expiration. Découvrez le nettoyage des groupes de processus, la révocation de l'état d'exécution et les méthodes d'audit.

Les sous-processus orphelins peuvent maintenir les identifiants actifs

Un agent IA arrivé à expiration n'est pas forcément arrêté. Si son wrapper shell, son compilateur, son assistant HTTP ou sa commande SSH a créé un enfant avant l'expiration, cet enfant peut continuer après la disparition du PID parent. S'il a hérité d'un identifiant utilisable ou d'une connexion déjà authentifiée, le délai d'expiration n'a pas mis fin à l'autorité que vous pensiez avoir révoquée.

J'ai vu des comptes rendus d'incident qui commencent par « l'agent a été tué à 14 h 03 » et se terminent par un appel API à 14 h 11. En général, personne n'a découvert un exploit brillant. Le runner a tué un processus, tandis que le travail utile s'était déjà déplacé dans un autre. Le nettoyage des processus ressemble à de la plomberie jusqu'au jour où un agent autonome peut utiliser des identifiants de production. À ce moment-là, la plomberie fait partie de la frontière de sécurité.

La mort du parent ne met pas fin au travail

Un processus enfant peut survivre à son parent, car le noyau suit les processus séparément, et non comme de simples extensions jetables d'une commande shell. Quand un parent se termine, le système d'exploitation réattribue ses enfants à un processus de récupération ou à un superviseur système. L'enfant conserve son propre PID, sa mémoire, ses descripteurs de fichiers, ses sockets, son répertoire courant et souvent son environnement.

Ce comportement est légitime. Les systèmes de build l'utilisent. Les multiplexeurs de terminaux l'utilisent. Les gestionnaires de services en dépendent. L'erreur consiste à prendre un PID parent pour la frontière d'exécution d'une session d'agent.

Considérez une chaîne familière :

agent-runner (PID 4102)
  shell tool wrapper (PID 4131)
    deployment script (PID 4140)
      ssh helper (PID 4144)

Le runner atteint son délai et envoie un signal au PID 4102. Si le shell ou le script de déploiement ne se termine pas avec lui, la chaîne restante peut continuer. Un cas plus délicat apparaît lorsque le script lance une tâche en arrière-plan avec \u0026, utilise nohup, appelle un gestionnaire de services ou demande à une machine distante de démarrer un travail. Le parent peut mourir proprement alors que l'enfant s'est déjà rendu indépendant.

C'est pourquoi un sous-processus orphelin d'agent est plus qu'un simple consommateur de CPU oublié. C'est la continuation non observée d'une décision qu'un humain ou un ordonnanceur pensait terminée. Les dommages dépendent de ce que le processus peut encore atteindre, mais l'échec commence plus tôt : le runner a choisi la mauvaise unité à arrêter.

POSIX documente les mécanismes à l'origine de ce comportement dans ses interfaces de processus et de contrôle des tâches. Un groupe de processus est un ensemble de processus liés qui partagent un identifiant de groupe. Les signaux peuvent viser le groupe plutôt qu'un seul membre. Une session regroupe un ou plusieurs groupes de processus et entretient normalement une relation avec un terminal de contrôle. Il s'agit de concepts distincts du noyau, et traiter ces termes comme des synonymes produit un code de nettoyage fragile.

Un délai d'expiration doit terminer une unité de confinement

Un délai d'expiration n'est sûr que lorsqu'il cible une unité de confinement établie avant le démarrage de l'agent. Pour un arbre de commandes local, cette unité est généralement un groupe de processus dédié. Le runner enregistre immédiatement l'identifiant du groupe, puis envoie les signaux à ce groupe lorsque le délai expire.

La séquence compte. N'attendez pas le nettoyage pour découvrir les enfants. À ce moment-là, le parent d'origine peut avoir disparu, la relation PPID peut déjà être trompeuse et une autre exécution peut avoir commencé.

Un chemin d'expiration pratique comporte cinq actions :

  1. Créer un groupe dédié ou une tâche gérée par un superviseur avant de lancer l'agent.
  2. Enregistrer l'identifiant d'exécution, le PID, le PGID, l'heure de début, la commande et le délai dans un même enregistrement.
  3. À l'expiration, marquer l'exécution comme expirée avant d'envoyer un signal afin que les nouvelles actions ne puissent pas être confondues avec du travail approuvé.
  4. Envoyer TERM à l'unité de confinement enregistrée, attendre brièvement pendant une période de grâce définie, puis envoyer KILL uniquement aux survivants.
  5. Capturer le résultat et conserver les heures d'envoi des signaux, l'état de sortie et les PID survivants.

L'enregistrement vient en premier, car le nettoyage peut entrer en concurrence avec la fin rapide d'un processus. Si votre journal dit seulement « agent tué », il ne peut pas répondre à ces questions : quel processus avez-vous tué, quels enfants partageaient son groupe et un enfant s'est-il échappé avant le signal ?

Sur macOS, examinez les identifiants au lieu de faire confiance à l'affichage d'un arbre de processus. Cette commande montre les champs qui expliquent la plupart des échecs liés aux délais :

ps -axo pid,ppid,pgid,sid,lstart,etime,command

Une exécution arrivée à expiration peut produire une sortie de cette forme avant le nettoyage :

  PID  PPID  PGID   SID  STARTED                  ELAPSED COMMAND
 4102  3988  4102  4102  Thu Jul 24 14:00:02 2026   00:31 agent-runner ...
 4131  4102  4102  4102  Thu Jul 24 14:00:02 2026   00:31 /bin/sh -c ...
 4144  4131  4102  4102  Thu Jul 24 14:00:04 2026   00:29 ssh ...

Ces valeurs sont données à titre d'illustration, et non comme un modèle à coder en dur. Le fait utile est que l'exécution possède un PGID connu, 4102. Le nettoyage envoie le signal à ce groupe, et pas seulement au PID 4102. Une cible négative indique à kill qu'il doit envoyer le signal à un groupe de processus :

kill -TERM -4102

Vérifiez la syntaxe pour le runtime et le système d'exploitation que vous utilisez. Certains wrappers interprètent mal les nombres négatifs ou prennent un argument supplémentaire pour une option de commande. Ne fondez pas une frontière de sécurité sur une ligne de commande que vous n'avez pas testée lors d'une véritable expiration.

Un signal de groupe a tout de même ses limites. Un enfant peut appeler setsid, créer un nouveau groupe ou une nouvelle session, confier le travail à un autre service ou démarrer un processus sur une machine distante. La discipline des groupes ferme la voie d'échappement locale habituelle, mais pas toutes les voies possibles. Votre runner doit traiter ces transitions comme des transferts explicites, avec leur propre mécanisme d'annulation et leur propre piste d'audit.

Les groupes de processus, les sessions et les identifiants répondent à des problèmes différents

Un groupe de processus vous donne une cible locale pour les signaux. Une session fournit une frontière plus large et peut isoler le contrôle des tâches du terminal. Ni l'un ni l'autre ne révoque un identifiant qu'un processus a copié en mémoire ou écrit sur disque. Ce sont des contrôles distincts qui exigent des preuves distinctes.

Cette différence est souvent brouillée parce que l'échec prend la forme d'un seul événement : un agent arrive à expiration et un appel ultérieur réussit. La couche de nettoyage demande : « Quels processus locaux doivent s'arrêter ? » La gestion des identifiants demande : « Quel processus peut encore autoriser cette action externe ? » L'audit demande : « Pouvons-nous prouver quelle action a eu lieu après la fin de l'approbation ? » Un groupe de processus ne répond qu'à la première question.

Les variables d'environnement sont le mauvais transfert classique. Si un runner exporte API_TOKEN, chaque descendant le reçoit, à moins qu'un processus ultérieur ne nettoie l'environnement. Un enfant peut copier le jeton, le transmettre à un autre programme ou maintenir une connexion authentifiée. Remplacer une variable d'environnement après le démarrage du parent ne rétracte pas les octets déjà hérités.

Les fichiers et les sockets sont tout aussi importants. Un processus peut hériter d'un descripteur de fichier ouvert qui pointe vers un fichier d'identifiants, d'une socket HTTPS connectée avec son état de session ou d'une socket d'agent SSH. Un processus en arrière-plan peut continuer à utiliser ce descripteur après la fin de son parent. close-on-exec aide à empêcher l'héritage accidentel lors d'un nouvel exec, mais ne fait rien lorsque l'enfant possède déjà le descripteur ou lorsque le programme le transmet délibérément.

SSH mérite une attention particulière. Une clé privée chargée dans un assistant local, une socket d'agent SSH, une connexion de contrôle multiplexée et une commande distante correspondent à quatre durées de vie différentes. Tuer le parent local peut laisser une connexion existante ou une commande distante en cours. Si l'agent peut lancer des commandes SSH arbitraires, le runner doit savoir s'il a autorisé l'appel d'un assistant local, un canal authentifié ou une tâche distante. Appeler ces trois éléments « la commande SSH » masque la frontière que vous devez contrôler.

Le modèle plus sûr consiste à transmettre une demande d'action, et non le secret brut. Le processus demande à un composant d'autorité d'effectuer une requête HTTP ou une opération SSH précise. Ce composant conserve le secret, décide si l'exécution actuelle peut l'utiliser, effectue l'action et renvoie le résultat. Un enfant échappé peut toujours répéter des requêtes tant que son exécution reste autorisée. Le composant d'autorité doit donc aussi comprendre l'expiration et la révocation.

Sallyport applique cette séparation pour ses canaux HTTP et SSH pris en charge : l'agent envoie sa demande via son shim MCP, tandis que l'application conserve les éléments API ou SSH et exécute elle-même l'action au lieu de transmettre le secret au processus de l'agent. Cela ne remplace pas le nettoyage des processus, mais élimine la forme la plus simple d'héritage des identifiants.

Le lancement en arrière-plan est une porte de sortie, pas un détail inoffensif du shell

L'échec de nettoyage le plus courant commence par une commodité du shell. Quelqu'un écrit command \u0026, démarre un pipeline, utilise nohup ou lance un runtime qui crée des workers. La commande parent semble terminée ou arrive à expiration, tandis que le worker continue.

Un pipeline shell mérite une attention particulière. Un runner peut exécuter /bin/sh -c 'generator | uploader' et enregistrer le PID du shell. Le shell possède des enfants pour les deux côtés du pipeline. Selon la manière dont le runner crée le groupe, tuer uniquement le shell peut laisser le générateur ou l'uploader en vie. Si l'uploader possède un identifiant et une connexion réseau, c'est précisément le processus que vous deviez arrêter.

nohup est plus problématique que beaucoup ne l'admettent. Il empêche un processus de recevoir un signal de fermeture du terminal ; il ne rend pas le processus fiable et ne le signale pas à votre runner. En automatisation, cela signifie généralement que quelqu'un veut que le travail survive à la fermeture du terminal ou du parent. Cela peut être valable pour un service géré, mais le travail doit alors passer sous le contrôle d'un superviseur qui en possède la gestion et en enregistre le cycle de vie. Il ne devrait pas se retrouver par accident dans un appel d'outil d'agent.

Les enfants détachés posent un autre type de problème. Dans Node.js, spawn avec detached: true donne délibérément à l'enfant un nouveau groupe de processus et une nouvelle session sur les systèmes de type Unix. Ce comportement peut être utile pour une application de bureau qui doit survivre à son lanceur. C'est un mauvais choix par défaut dans un exécuteur d'agent, car il neutralise l'arrêt normal du groupe par l'exécuteur.

import { spawn } from "node:child_process";

const child = spawn("/bin/sh", ["-c", "sleep 600"], {
  detached: true,
  stdio: "ignore"
});
child.unref();

Cet extrait sert de cas de test pour le nettoyage, et non de modèle d'exécution à copier dans un runner d'agent. Il crée un enfant qui ne partage plus le groupe de processus normal de son parent. Si un framework d'outils peut exécuter ce type de code, le nettoyage par groupe de processus seul ne peut pas vous donner une garantie complète. Limitez l'exécution détachée, interceptez-la à la frontière de l'outil ou exécutez l'outil dans un confinement plus strict du système d'exploitation.

La recommandation populaire consistant à « tuer l'arbre des processus » échoue également lorsqu'elle signifie parcourir récursivement les PPID. Les PPID décrivent un état à un instant donné, et non une frontière durable d'appartenance. La réattribution les modifie. Un enfant peut créer un processus entre votre parcours et votre signal. Un descendant peut sortir de l'arbre. Enregistrez une identité de groupe ou de tâche au lancement, puis faites de toute sortie de cette identité une opération délibérée et auditable.

Ne nettoyez pas par nom de commande

Gardez les clés SSH confinées
Utilisez l'assistant SSH intégré à Sallyport afin que le matériel des clés privées n'atteigne jamais l'agent.

pkill et les correspondances larges par nom sont séduisants parce qu'ils sont courts. Ils conviennent mal à des exécutions d'agents concurrentes. pkill ssh peut terminer une connexion interactive sans rapport. pkill python peut supprimer l'outil local d'un développeur. Une correspondance sur une chaîne d'arguments peut manquer un processus qui a modifié ses arguments ou correspondre par erreur à une autre exécution.

Utilisez le nom de commande comme indice d'investigation uniquement. Pour l'application de la règle, utilisez une frontière de propriété.

Un runner qui connaît un groupe de processus peut d'abord examiner ses membres. Sur macOS, filtrez la table des processus avec le numéro enregistré, et non avec un nom d'exécutable deviné :

ps -axo pid,ppid,pgid,sid,etime,command | awk '$3 == 4102 || NR == 1'

La première colonne est le PID, la deuxième le PPID et la troisième le PGID pour cette disposition de commande. Dans le code de production, évitez d'analyser du texte destiné à l'utilisateur lorsque votre langage peut appeler des API natives de processus. Pour un terminal opérateur, cette sortie permet une vérification rapide et lisible avant de choisir un signal.

Envoyez ensuite le signal au groupe et vérifiez à nouveau :

kill -TERM -4102
sleep 2
ps -axo pid,ppid,pgid,sid,etime,command | awk '$3 == 4102 || NR == 1'

Si des membres restent, cherchez pourquoi avant d'envoyer machinalement KILL. Un processus bloqué dans une opération noyau non interruptible nécessite une enquête différente de celle d'un processus qui a ignoré TERM. Un processus doté d'un autre PGID n'a pas survécu à votre signal par accident : il a franchi la frontière. C'est la preuve d'une décision de conception, du comportement d'un framework ou d'un outil malveillant.

Après la période de grâce définie, KILL convient aux processus locaux qui restent dans le groupe enregistré et qui ne doivent pas terminer leur travail. Ne promettez pas un nettoyage gracieux lorsque l'agent a dépassé sa fenêtre d'autorité. La grâce sert à fermer les fichiers et à enregistrer l'état, pas à poursuivre indéfiniment un travail externe.

Les conteneurs et les gestionnaires de services peuvent fournir une meilleure propriété qu'un groupe de processus brut lorsque vous contrôlez l'environnement d'exécution. Les workflows d'agents de bureau macOS ne bénéficient pas automatiquement des cgroups Linux. N'appliquez donc pas des conseils sur les cgroups comme s'ils étaient valables tels quels. Sur un Mac, un groupe de processus enfant dédié, des permissions d'outils limitées et un contrat explicite pour les tâches distantes constituent souvent la base. Si vous avez besoin de plafonds de ressources ou d'un confinement complet des descendants, utilisez un environnement d'exécution qui fournit réellement ces contrôles.

L'audit doit survivre au parent

Vous ne pouvez pas reconstituer une action échappée à partir du texte final de l'agent. Le parent peut être mort avant d'avoir vidé ses journaux, et un enfant qui a continué à fonctionner n'a aucune raison de faire son propre compte rendu. Construisez l'audit autour d'événements immuables observés en dehors du processus de l'agent.

Pour chaque exécution, enregistrez au moins les champs suivants avant d'autoriser un travail externe :

{
  "run_id": "run-7f3c",
  "started_at": "2026-07-24T14:00:02Z",
  "deadline_at": "2026-07-24T14:00:32Z",
  "parent_pid": 4102,
  "process_group": 4102,
  "approval_identity": "signed agent process identity",
  "state": "active"
}

L'enregistrement d'action doit inclure l'identifiant d'exécution, une séquence d'action, l'heure à laquelle la passerelle l'a acceptée, le canal, l'identité de la cible et le résultat. Par défaut, ne journalisez jamais les valeurs bearer brutes, les clés privées, les en-têtes d'autorisation ou le corps complet des requêtes. Un enregistrement qui expose l'identifiant tout en documentant son utilisation crée un deuxième incident.

Lorsque l'expiration se produit, ajoutez une transition d'état avant d'envoyer quoi que ce soit :

{
  "run_id": "run-7f3c",
  "event": "deadline_expired",
  "observed_at": "2026-07-24T14:00:32Z",
  "process_group": 4102,
  "signal": "TERM"
}

Votre enquête dispose alors d'une question précise et d'une réponse précise : une action externe a-t-elle commencé après deadline_expired ? Si la passerelle accepte une action après cet événement, soit elle n'a pas appliqué l'état de l'exécution, soit l'appelant ne portait pas l'identité attendue par la passerelle. Si une action a commencé avant l'expiration mais s'est terminée après, dites-le clairement. Le début et la fin sont deux horodatages différents, et les confondre fait passer un travail en cours normal pour une fuite.

Conservez l'heure civile pour les humains et une valeur monotone du temps écoulé pour ordonner les événements sur une même machine. Les horloges peuvent changer à cause de la synchronisation ou d'un réglage manuel. Pendant un incident, vous n'avez pas besoin d'un cours sur la mesure du temps ; il vous faut assez de données pour éviter d'affirmer qu'une action a eu lieu après la mort du parent lorsque les horloges ne concordent pas.

Les journaux Sessions et Activity de Sallyport séparent la vue de l'exécution des appels individuels, et son journal chiffré chaîné par hachage peut être vérifié hors ligne avec sp audit verify. C'est utile après la disparition d'un parent, car la piste d'audit ne dépend pas de la bonne volonté de l'agent pour rapporter sa propre histoire.

Une piste d'audit est une preuve, pas un mécanisme de confinement. Elle indique qu'un appel a été effectué et facilite l'examen ultérieur. Elle ne termine pas un orphelin local, ne rétracte pas les données qui lui ont déjà été renvoyées et n'annule pas une commande distante dépourvue de mécanisme d'annulation. Traitez ces responsabilités séparément et rendez chacune visible dans l'enregistrement de l'exécution.

Reconstituez l'échec dans le bon ordre

Repérez les appels après expiration
Le journal d'activité enregistre chaque appel HTTP ou SSH indépendamment de la sortie finale de l'agent.

Lorsque vous trouvez un orphelin présumé, préservez les faits avant de nettoyer la scène. Un kill -9 précipité peut être justifié pour arrêter un travail nuisible, mais il peut effacer les relations entre processus dont vous avez besoin pour corriger le runner. Capturez d'abord un instantané des processus lorsque la situation le permet.

Commencez par l'enregistrement de l'exécution arrivée à expiration. Notez son PID parent, son PGID ou son identité de tâche, son heure de début, son délai et chaque signal envoyé. Prenez ensuite l'instantané actuel des processus. Sur macOS, comparez le PID, le PPID, le PGID, le SID, le temps écoulé et la commande. Un orphelin a souvent un PPID de 1 ou celui d'un superviseur, mais n'en faites pas votre seul test. Un processus peut rester un risque actif alors que son PPID pointe encore ailleurs.

Classez ensuite ce qui s'est échappé :

  • Un processus du PGID d'origine a survécu au signal attendu.
  • Un processus possède un nouveau PGID ou SID et s'est donc détaché localement.
  • Un processus local a demandé un démarrage distant et la tâche distante a continué.
  • Un identifiant ou un canal authentifié est resté utilisable après l'expiration de l'exécution.

Chaque catégorie appelle une correction différente. La première renvoie à la gestion des signaux ou au moment du nettoyage. La deuxième indique un mécanisme de détachement non approuvé. La troisième nécessite un identifiant de tâche distante et un protocole d'annulation. La quatrième exige que l'autorité d'action lie les requêtes à une exécution actuelle au lieu de faire confiance au processus parent.

Faites ensuite correspondre les enregistrements d'action à la chronologie. Ne commencez pas par lire les transcriptions de l'agent. Cherchez les requêtes acceptées après l'expiration, les cibles différentes de l'instruction d'origine, les appels répétés qui poursuivent un lot et les appels que le parent ne pouvait pas avoir signalés parce qu'il était déjà terminé. La transcription peut expliquer l'intention plus tard. Elle ne peut pas prouver qu'une requête réseau a eu lieu ou non.

Enfin, décidez si l'identifiant doit être renouvelé ou révoqué. Si aucun secret brut n'est entré dans l'agent et si la passerelle d'action a refusé les appels postérieurs à l'expiration, le risque restant peut se limiter aux données ou aux modifications déjà réalisées. Si l'enfant a hérité d'un jeton bearer, d'une clé privée, d'un accès à l'agent SSH ou d'un canal administratif connecté, supposez qu'il peut encore agir jusqu'à ce que vous ayez désactivé cette autorité. C'est à ce moment que les équipes perdent du temps à discuter de la question de savoir si le processus a « probablement » fait quelque chose. Si vous ne pouvez pas établir qu'il a perdu l'accès, supprimez cet accès.

Un test d'échec doit rendre l'orphelin évident

Déplacez l'autorité au-delà du runner
Les agents demandent des actions HTTP et SSH via le shim MCP, tandis que Sallyport les exécute lui-même.

Un harnais de test des délais doit échouer clairement lorsqu'un enfant survit. Tester uniquement le fait que le parent renvoie une expiration ne prouve presque rien.

Créez un scénario de test qui démarre un parent et un enfant dans le même groupe enregistré. L'enfant doit attendre assez longtemps pour que le harnais puisse faire expirer le parent. Le parent doit signaler le PID et le groupe de l'enfant avant de se bloquer. Après l'expiration, vérifiez qu'aucun des deux processus n'existe et que le journal des actions de test ne contient aucun appel accepté après l'enregistrement de l'expiration.

Ce scénario shell montre la forme du problème :

#!/bin/sh
(
  trap 'exit 0' TERM INT
  sleep 600 \u0026
  child=$!
  printf 'parent=%s child=%s pgid=' "$$" "$child"
  ps -o pgid= -p "$$" | tr -d ' '
  wait "$child"
)

Exécutez-le avec le même code de lancement que celui utilisé par votre exécuteur d'agent, et non depuis un terminal interactif qui fournit discrètement un comportement différent pour le contrôle des tâches. Capturez les identifiants affichés, forcez l'expiration et vérifiez la table des processus. Si l'enfant reste en vie, vous avez appris quelque chose de précis : soit votre lancement n'a pas créé le groupe supposé, soit votre signal d'expiration ne l'a pas ciblé.

Répétez le test pour les outils que vos agents utilisent réellement. Les wrappers shell, les API de processus enfants de Node, les appels subprocess de Python, les gestionnaires de paquets, les compilateurs et les assistants SSH font des choix différents concernant les groupes, les descripteurs et la propagation des signaux. Vous n'avez pas besoin de centaines de tests. Il vous faut un test destructif pour chaque modèle de lanceur capable de créer des descendants.

Ajoutez un second scénario qui tente de se détacher. Le test peut utiliser un appel qui crée une session ou une option de runtime qui crée un enfant détaché. Le résultat attendu doit être un refus, une prise en charge par le superviseur ou un événement d'enregistrement explicite. Un succès silencieux est un défaut, car il indique à l'agent qu'il peut déplacer le travail en dehors de la frontière du délai.

Ne vous arrêtez pas aux PID locaux. Si votre agent peut démarrer un travail distant, demandez au côté distant de renvoyer un identifiant de tâche et testez l'annulation après l'expiration locale. L'état recherché n'est pas « le client SSH est sorti ». L'état recherché est « la tâche distante identifiée par cette exécution est arrêtée ou son travail restant a un responsable clairement désigné ».

L'approbation doit expirer avant le processus

L'approbation humaine d'une exécution d'agent ne signifie pas grand-chose si un enfant peut l'utiliser après que l'exécution a été déclarée morte. Liez l'autorité d'action externe à une identité et à un état d'exécution que le composant d'autorité vérifie pour chaque requête. Lorsque le runner marque l'exécution comme expirée, les nouvelles actions de cette exécution doivent échouer, même si un orphelin local dispose encore de temps processeur.

Cette conception ne signifie pas qu'il faut demander à un humain son accord pour chaque commande shell. Cela signifie que le composant capable de dépenser l'autorité sait quand l'exécution s'est terminée. Il peut fournir à une exécution un accès de courte durée, le refuser après révocation et enregistrer le refus. L'arrêt du groupe de processus limite alors les dommages locaux, tandis que la frontière d'action limite les conséquences d'une course lors du nettoyage.

Conservez une approbation par action pour les opérations qui l'exigent, mais ne confondez pas approbation et supervision. Un utilisateur peut approuver un processus d'agent signé au début d'une session. Si un descendant continue après la sortie de l'agent, il a dépassé la durée d'exécution approuvée, même s'il a hérité du même contexte local. Votre système doit rendre cette condition applicable, au lieu de compter sur la politesse de l'enfant.

Le runner doit aussi distinguer l'annulation d'une exécution de l'annulation d'un effet externe. Une requête annulée peut déjà avoir atteint l'API. Un client SSH terminé peut déjà avoir démarré une commande distante. Enregistrez séparément la demande d'annulation, la sortie locale observée et toute confirmation renvoyée par le système externe. Cette formulation est moins rassurante que « annulé », mais elle indique aux opérateurs ce qu'ils savent réellement.

La première correction que j'apporterais à un exécuteur d'agent est modeste et peu spectaculaire : créer et enregistrer un groupe de processus dédié au lancement, puis terminer ce groupe dans le chemin d'expiration et vérifier le résultat dans un test d'échec. Associez-y une frontière d'action qui refuse toute exécution après son expiration. Une fois ces deux faits établis, un orphelin devient un incident que vous pouvez contenir et expliquer, plutôt qu'une crainte vague qu'un processus fonctionne encore.

FAQ

Qu'est-ce qu'un sous-processus orphelin dans une exécution d'agent IA ?

Un processus orphelin est un processus dont le parent d'origine s'est terminé, si bien que le système d'exploitation lui attribue un nouveau parent. Il peut continuer à fonctionner, garder des connexions réseau ouvertes et utiliser les identifiants qu'il a déjà reçus. L'expiration du parent prouve seulement qu'un PID est mort.

Tuer un agent IA tue-t-il aussi ses processus enfants ?

Seulement si le runner démarre l'agent dans un groupe de processus isolé ou une frontière de confinement équivalente, puis termine toute cette frontière. Tuer le PID parent seul laisse intacts les enfants créés indépendamment. Testez ce comportement avec un enfant qui dort après la fin de son parent.

Dois-je utiliser un groupe de processus ou une session pour les délais d'expiration d'un agent ?

Pour un arbre de commandes local sur macOS, utilisez un groupe de processus, puis envoyez le signal à l'identifiant négatif du groupe après l'expiration. Une session peut fournir une frontière plus large, tandis qu'un gestionnaire de services ou un conteneur peut imposer un contrôle plus strict des ressources. Choisissez une frontière et enregistrez son identifiant avant le début du travail.

Un processus orphelin peut-il encore utiliser une clé API après l'arrêt de l'agent ?

Un socket de contrôle SSH, une connexion authentifiée ouverte, un descripteur de fichier hérité ou un jeton bearer de longue durée peuvent rester utilisables après la mort du parent. La solution consiste à éviter de transmettre des identifiants bruts au processus de l'agent et à donner à chaque action externe une durée de vie limitée. La révocation doit couvrir le chemin d'action, pas seulement la session de discussion.

Comment trouver les processus enfants laissés par un agent arrivé à expiration sur macOS ?

Sur macOS, examinez le PID, le PPID, le PGID, le SID, le temps écoulé et la ligne de commande avec ps avant d'envoyer un signal. Cherchez les processus dont le PPID est devenu 1 ou celui d'un autre superviseur et dont le PGID correspond encore à l'exécution expirée. Conservez cet instantané avant le nettoyage si vous devez constituer un dossier d'incident.

`pkill` est-il sûr pour nettoyer les agents IA arrivés à expiration ?

Non. Une correspondance par nom peut tuer un éditeur, un runner de tests ou une autre exécution d'agent sans rapport, tout en manquant un enfant qui a changé de nom d'exécutable. Envoyez plutôt le signal à un groupe de processus enregistré ou à une tâche gérée par un superviseur.

Que doit enregistrer l'audit d'une exécution d'agent IA ?

Commencez par le délai, le groupe de processus ou l'identifiant de tâche, le PID parent, l'identité d'action approuvée et les horodatages issus d'une horloge monotone. Ajoutez chaque appel externe, le statut de sortie et chaque signal de nettoyage. Sans identifiant de frontière, l'attribution ultérieure devient une supposition.

Une passerelle d'action peut-elle empêcher les fuites d'identifiants par des processus orphelins ?

Cela aide seulement si la passerelle conserve l'identifiant en dehors de l'agent et journalise l'action indépendamment de sa propre sortie. Il faut tout de même un confinement au niveau du système d'exploitation, car un processus orphelin peut consommer les fichiers, le temps processeur, les sockets et les données qu'il a déjà obtenus. Une passerelle enregistre ce qui franchit sa frontière, mais elle ne peut pas faire disparaître un arbre de processus mal conçu.

Que faire après avoir découvert un processus d'agent orphelin ?

Commencez par terminer le groupe enregistré, attendez brièvement, puis ne passez à une mesure plus forte que pour les survivants. Capturez ensuite les processus restants et faites correspondre leur heure de démarrage, leur groupe, leur activité réseau ouverte et les journaux d'action. Faites tourner ou révoquez un identifiant lorsque vous ne pouvez pas établir que le processus échappé a perdu son accès.

Comment tester le nettoyage après expiration pour un agent de programmation autonome ?

Lancez un test d'échec volontaire dans la CI : laissez un parent créer un enfant, faites dépasser au parent son délai d'expiration, puis vérifiez qu'aucun des deux PID ne subsiste et qu'aucune action postérieure à l'expiration n'a été acceptée. Répétez le test avec les wrappers shell, les runtimes et les assistants SSH, car chacun peut ajouter une couche de processus. Un nettoyage que vous n'avez jamais testé n'est qu'un espoir.

Sallyport

Sallyport exécute les appels d'API et les commandes SSH à la place de votre agent IA. Les clés restent dans un coffre-fort local sur votre Mac ; vous approuvez chaque exécution et chaque action est consignée dans un journal scellé.

© 2026 Sallyport · Open source sous Apache-2.0 · Oleg Sotnikov