Pourquoi séparer SSH stdout et stderr ?
Séparer SSH stdout et stderr donne aux agents des données fiables, des diagnostics utiles, un ordre honnête et un résultat de sortie clair.

Le résultat d'une commande SSH doit conserver stdout, stderr et la terminaison comme des faits distincts. Si vous les aplatissez dans une seule chaîne, l'agent ne peut plus distinguer les données du diagnostic, l'opérateur ne voit pas pourquoi la commande a échoué et une vue d'audit risque de présenter une séquence qui n'a jamais existé.
La solution ne consiste pas à choisir un séparateur plus élégant. Gardez les deux flux d'octets séparés, enregistrez un ordre d'observation limité lorsque le transport en fournit un et représentez indépendamment l'état de sortie, le signal de sortie, l'expiration du délai, l'annulation et l'erreur de transport. Ce modèle demande un peu plus de travail à la frontière de capture et supprime une longue série d'erreurs d'analyse en aval.
SSH distingue déjà les deux flux
SSH transporte les données ordinaires du canal et stderr au moyen de messages de protocole différents. La RFC 4254 les appelle SSH_MSG_CHANNEL_DATA et SSH_MSG_CHANNEL_EXTENDED_DATA ; elle attribue le type de données étendues 1 à SSH_EXTENDED_DATA_STDERR. Une bibliothèque cliente qui fournit deux lecteurs séparés expose une distinction que le protocole a volontairement conservée.
Cette distinction a un sens. Les programmes écrivent généralement les résultats destinés à une machine dans stdout et les diagnostics dans stderr. Une commande peut produire un JSON valide dans stdout, afficher un avertissement dans stderr et tout de même renvoyer zéro. Une autre peut produire une sortie partielle, décrire un échec dans stderr et renvoyer une valeur différente de zéro. Les octets seuls ne permettent pas de savoir quel cas s'est produit.
La fusion au moment de la capture détruit une information qu'aucun analyseur ne pourra retrouver. Des préfixes comme [stderr] aident un lecteur humain, mais modifient le contenu. Les séparateurs de ligne sont pires : un fragment peut se terminer sans retour à la ligne, les données binaires peuvent contenir n'importe quel octet et le séparateur ajouté peut transformer deux fragments valides en un document invalide.
Traitez chaque flux comme des octets jusqu'à ce qu'un consommateur choisisse une politique de décodage. UTF-8 est fréquent, mais SSH ne le garantit pas. Même des outils apparemment textuels peuvent émettre des séquences invalides à cause d'une différence de paramètres régionaux, de noms de fichiers contenant des octets arbitraires ou d'une écriture coupée au milieu d'un caractère multioctet. Stockez les octets bruts ou un encodage sans perte, puis proposez le texte décodé comme une vue.
Le fait protocolaire compte parce qu'il déplace la charge de la preuve. Si votre type de résultat ne contient que output: string, il décrit faussement ce que SSH a livré. Le rendu de confort doit intervenir après la capture, là où il peut être remplacé sans réécrire le journal d'audit.
L'identité du flux n'indique pas la gravité
Stderr signifie descripteur de fichier 2, pas échec. Traiter chaque octet de stderr comme une erreur produit des agents bruyants qui relancent des commandes réussies, jettent un stdout exploitable ou demandent une approbation après des avertissements sans conséquence.
De nombreux programmes connus utilisent stderr pour la progression, les traces détaillées, les invites et les avertissements. Un compilateur peut réserver stdout au contenu généré et signaler sa progression ailleurs. Une commande peut aussi échouer silencieusement avec un état non nul. Cette relation constitue un indice utile, pas une règle booléenne.
Distinguez au moins quatre notions :
stdoutetstderrindiquent la provenance des octets.exit_statusouexit_signaldécrit la manière dont le programme distant s'est terminé.transport_errorindique si l'opération SSH elle-même s'est achevée.timed_outetcancelleddécrivent une intervention locale.
Cette séparation évite une erreur classique : transformer stderr != empty en success = false. En règle générale, le succès doit signifier que la commande a démarré, que le canal s'est achevé et que l'état distant vaut zéro. Votre application peut appliquer une règle plus stricte à une commande précise, mais cette règle appartient à l'adaptateur de la commande, pas à l'exécuteur SSH générique.
L'erreur inverse est tout aussi néfaste. Certaines enveloppes renvoient seulement stdout en cas de réussite et remplacent tout le résultat par une exception en cas d'échec. L'exception peut contenir une fin tronquée de stderr alors que le stdout partiel disparaît. L'agent reçoit moins d'éléments au moment même où il en a le plus besoin.
Ne surchargez pas un seul champ error avec les diagnostics distants, les erreurs de connexion, les délais expirés et les erreurs d'analyse. Ces situations n'appellent pas la même stratégie de nouvelle tentative. Une erreur DNS peut justifier un nouvel essai. Un état 2 dû à une erreur d'utilisation ne le justifie généralement pas. Si stdout contient un JSON invalide, conservez les octets d'origine afin qu'un développeur puisse déterminer si le problème vient de la commande ou de l'analyseur.
Le contrat doit conserver les faits avant l'interprétation
Un objet de résultat durable contient les éléments bruts et rend les états inconnus explicites. Il ne doit pas obliger chaque appelant à reconstruire les faits à partir d'une transcription mise en forme.
Ce contrat est volontairement banal :
{
"stdout": {"encoding": "base64", "data": "Li4u", "truncated": false},
"stderr": {"encoding": "base64", "data": "Li4u", "truncated": false},
"events": [
{"seq": 1, "stream": "stdout", "offset": 0, "length": 48},
{"seq": 2, "stream": "stderr", "offset": 0, "length": 19}
],
"termination": {
"kind": "exit",
"exit_status": 0,
"exit_signal": null,
"core_dumped": null
},
"transport_error": null,
"started_at": "2026-07-24T10:20:30.123Z",
"finished_at": "2026-07-24T10:20:31.456Z"
}
Les deux objets de flux contiennent le contenu de référence. Chaque événement pointe vers une plage d'octets au lieu de recopier le texte, ce qui permet à une vue de produire une transcription sans dupliquer la charge utile. seq désigne uniquement l'ordre d'observation lors de la capture. Il n'affirme pas que les écritures distantes se sont produites exactement dans cet ordre.
Le champ termination.kind doit couvrir au minimum exit, signal, timeout, cancelled, transport_error et unknown. Utilisez des champs pouvant valoir null au lieu de codes magiques. L'absence d'état de sortie SSH ne vaut pas zéro et un délai local expiré ne vaut pas l'état 124, sauf si un shell ou l'utilitaire timeout a effectivement produit 124 sur l'hôte distant.
Indiquez la troncature pour chaque flux. Un indicateur global truncated ne permet pas de savoir si l'analyseur possède encore un JSON complet dans stdout ou s'il a simplement perdu la fin d'un stderr très détaillé. Enregistrez le nombre d'octets capturés et rejetés lorsque vous le connaissez. Si vous ne conservez qu'un préfixe et un suffixe, modélisez ces parties comme des segments distincts au lieu de les joindre comme si le milieu n'avait jamais existé.
Les horodatages facilitent la mesure de latence et l'enquête, mais ne les utilisez pas pour trier les fragments à l'aide de l'heure murale. Une horloge peut sauter et deux lecteurs concurrents peuvent recevoir le même horodatage à la résolution choisie. Attribuez le compteur de séquence en un point de sérialisation unique. Conservez séparément une durée monotone si votre environnement d'exécution en fournit une.
Versionnez le contrat avant que des clients n'en dépendent. L'ajout de champs est généralement sûr, mais faire passer events.seq de l'ordre d'arrivée à l'ordre d'affichage rompt la sémantique même si la forme du JSON reste identique.
L'ordre entre les flux a une limite stricte
Vous pouvez conserver l'ordre dans lequel votre pile SSH observe les messages du canal, mais vous ne pouvez généralement pas prouver l'ordre des écritures du programme distant entre stdout et stderr. Cette limite doit figurer dans le modèle de données et dans le texte de l'interface.
À l'intérieur d'un flux, les octets restent ordonnés. Entre deux flux, plusieurs couches ajoutent des tampons : l'environnement du langage distant, libc, les tubes, le serveur SSH, les paquets de transport, la bibliothèque cliente et vos propres tâches de lecture. Stdout peut être mis en tampon par blocs lorsqu'il n'est pas relié à un terminal, tandis que stderr peut être vidé plus tôt. Une écriture ultérieure dans stderr peut donc devenir visible avant une écriture antérieure dans stdout.
La RFC 4254 conserve la séquence des messages de canal envoyés par l'implémentation SSH. C'est utile, et un callback de bibliothèque qui expose ces messages peut attribuer une séquence de réception fidèle. Dès qu'une bibliothèque sépare les données en lecteurs indépendants pour stdout et stderr, deux goroutines ou callbacks asynchrones se disputent le signalement des données disponibles. L'ordre dans lequel l'ordonnanceur les exécute constitue une observation de la livraison locale, pas une reconstruction de l'ordre du code source distant.
Cette petite commande montre pourquoi un test ne doit pas exiger une transcription fusionnée universelle :
sh -c 'printf "out-1\n"; printf "err-1\n" >&2; printf "out-2\n"; printf "err-2\n" >&2'
Un terminal affiche souvent l'ordre apparent du code. Si vous redirigez les deux descripteurs vers un fichier avec >all.log 2>&1, le shell les fait pointer vers la même destination, ce qui fournit à ce processus un seul chemin d'écriture géré par le noyau. Si vous les capturez avec des tubes distincts, l'observateur peut recevoir les fragments dans un autre ordre. Ajoutez un environnement de langage qui utilise des tampons et l'écart augmente.
Si une chronologie exacte entre les flux est obligatoire, modifiez le contrat du producteur. Faites écrire au programme distant des enregistrements structurés dotés de leur propre numéro de séquence dans un seul flux, ou dirigez les deux descripteurs vers une même destination distante avant que SSH ne les voie. Vous obtenez ainsi un ordre défini en renonçant aux flux indépendants chez le producteur. Un client SSH générique ne peut pas inventer après coup le fait manquant.
Le texte d'audit doit parler de séquence observée, pas de séquence d'exécution. Cette précision n'est pas une précaution juridique superflue. Elle évite à un enquêteur de confondre l'ordonnancement avec la causalité.
Les limites des fragments viennent du transport
Un callback de lecture n'est ni une ligne, ni un enregistrement, ni un appel distant à write. Les analyseurs qui font cette hypothèse fonctionnent pendant les tests et échouent sous charge.
Une écriture peut arriver en plusieurs fragments. Plusieurs écritures peuvent arriver dans un seul fragment. Un point de code UTF-8, une séquence d'échappement ANSI ou un jeton JSON peut traverser une limite. La même commande peut produire un découpage différent lors de l'exécution suivante sans que sa sortie ait changé.
Construisez la couche de capture autour d'opérations d'ajout d'octets. Pour chaque flux, ajoutez le fragment à son tampon ou à son fichier temporaire, puis enregistrez l'offset et la longueur obtenus. Si la bibliothèque expose les messages en série, attribuez seq à cet endroit. Si elle fournit des lecteurs indépendants, envoyez les notifications de fragments à un collecteur unique et documentez que la séquence reflète la réception par ce collecteur.
Le découpage en lignes appartient à une vue dérivée. Maintenez un décodeur progressif et un tampon de ligne inachevée pour chaque flux. Ne partagez jamais un tampon de ligne entre stdout et stderr, car un fragment stdout sans fin suivi d'une ligne stderr ne doit pas devenir une ligne artificielle. À la fermeture du flux, exposez la dernière ligne partielle au lieu de la supprimer en silence.
L'analyse JSON doit normalement attendre la fin de stdout et la connaissance de la terminaison de la commande. Un protocole JSON en continu est différent : il exige un cadrage explicite, tel que du JSON séparé par des retours à la ligne, un préfixe de longueur ou une grammaire incrémentale documentée. Deviner les limites des enregistrements d'après les fragments n'est pas du streaming, c'est une course.
La sortie binaire exige elle aussi un chemin explicite. Le base64 dans un JSON est simple et portable, même s'il augmente la taille. Une référence vers un blob peut convenir aux gros résultats si le système d'audit garantit la conservation et l'intégrité. Ne décodez pas avec des caractères de remplacement avant de jeter l'original. Le remplacement cache si l'altération provient de l'outil distant, de l'adaptateur de transport ou de la vue.
Les limites doivent s'appliquer pendant la lecture, pas après le chargement complet en mémoire. Continuez à vider les deux flux même si l'un dépasse sa limite de conservation, sinon le processus distant peut se bloquer sur un tube plein. Stockez le préfixe, le suffixe ou le fichier externe autorisé, comptez les octets rejetés et poursuivez la lecture jusqu'à la fermeture ou l'annulation.
Un pseudo-terminal échange la structure contre le comportement
Ne demandez pas de pseudo-terminal pour une commande dont stdout sera analysé. Un PTY est utile à une session humaine, mais il modifie l'environnement du programme et envoie souvent stdout et stderr vers le même périphérique terminal avant que le client SSH puisse préserver leur identité.
Les programmes vérifient si un descripteur correspond à un terminal. Ils peuvent activer les couleurs, dessiner une progression avec des retours chariot, couper les lignes à la largeur annoncée, demander une saisie ou passer d'un tampon par blocs à un tampon par lignes. Les octets capturés avec un PTY peuvent donc différer de ceux que produit la même commande sans PTY. Il s'agit d'un comportement observable, pas d'une simple option d'affichage.
Les options du client OpenSSH reflètent cette distinction : -T désactive l'allocation d'un pseudo-terminal, tandis que -t la demande et qu'un -t répété peut la forcer. L'automatisation doit fonctionner sans PTY par défaut. Demandez-en un uniquement lorsque le programme distant exige une sémantique de terminal et que le contrat de résultat indique explicitement que la séparation des flux n'est pas disponible.
Un PTY ne rend pas l'ordre plus vrai. Il peut fournir un seul flux d'octets de terminal, de sorte que l'ordre affiché est défini à cette frontière, mais le programme et ses bibliothèques peuvent modifier leur mise en tampon après avoir détecté le terminal. Vous avez échangé des éléments séparés contre un comportement interactif ; vous n'avez pas découvert la chronologie d'une exécution sans PTY.
Cette distinction explique une catégorie d'erreurs tenaces. Un développeur teste une commande dans un shell et voit une progression nette, colorée et dans un ordre cohérent. L'agent exécute le même texte sans PTY, stdout passe à un tampon par blocs, stderr apparaît d'abord et l'analyseur reçoit plus tard la sortie destinée aux machines sans codes de contrôle. Quelqu'un force alors un PTY pour rapprocher la transcription du test manuel, et l'analyse JSON échoue parce que des codes de couleur ou des invites entrent dans le flux.
Traitez l'exécution interactive et l'exécution structurée comme deux modes distincts de l'API. Le mode structuré doit promettre des flux séparés et une capture stable sans émulation de terminal. Le mode interactif doit renvoyer une transcription du terminal, ses dimensions et une indication explicite que l'identité initiale de stdout et stderr n'a pas été conservée. Un pty: true caché dans les options de la requête ne suffit pas si la réponse ressemble à un résultat structuré.
Les fichiers de démarrage distants ajoutent une autre difficulté. La RFC 4254 avertit que l'initialisation du shell peut produire une sortie parasite au démarrage d'un sous-système et recommande un marqueur reconnaissable pour les protocoles qui doivent la distinguer. La même leçon vaut pour les adaptateurs de commandes : appelez le chemin exécutable le plus direct que vous contrôlez, évitez les shells interactifs inutiles et traitez les premiers octets inattendus comme des éléments à conserver au lieu de supprimer silencieusement ce qui ressemble à une bannière.
Si une commande a réellement besoin d'une invite de mot de passe ou d'un contrôle du terminal, ne prétendez pas que sa transcription peut être analysée telle quelle. Donnez à l'agent un outil d'interaction spécialisé avec des entrées limitées et une transcription conçue pour la sémantique du terminal. Séparer ce chemin protège la garantie plus simple selon laquelle les actions SSH ordinaires renvoient un stdout fidèle, un stderr fidèle et un résultat de terminaison.
L'état de sortie fait partie du résultat
La RFC 4254 définit une requête de canal exit-status et une forme distincte exit-signal. Elle recommande de renvoyer l'état, mais permet aussi au client de l'ignorer. Votre API doit donc prévoir un résultat inconnu explicite au lieu de supposer une réussite lorsque aucun état n'arrive.
Un état égal à zéro indique généralement la réussite, pas une certitude. La RFC 4254 emploie cette formulation nuancée parce que les conventions des commandes se trouvent au-dessus du transport. L'état reste néanmoins le principal signal générique disponible. Conservez la valeur non signée fournie par le protocole avant de la convertir selon les conventions de processus du langage hôte.
Une terminaison par signal n'est pas un état de sortie négatif. Stockez séparément le nom du signal, l'indicateur de vidage mémoire lorsqu'il est fourni et le message explicatif distant. Si un consommateur souhaite afficher un nombre de type shell, comme 128 plus la valeur du signal, il peut le calculer pour l'affichage. Le journal d'audit doit garder les faits SSH.
Distinguez les résultats suivants dans le code et l'interface :
- La commande distante a renvoyé un état.
- Le côté distant a signalé une terminaison par signal.
- Le canal s'est fermé sans l'un ou l'autre rapport.
- Le client a échoué avant la confirmation du démarrage de la commande.
- La connexion a échoué après la réception d'une sortie partielle.
Le quatrième cas ne doit pas se déguiser en état distant 255 sous prétexte que le client OpenSSH en ligne de commande utilise souvent 255 pour ses propres erreurs. Une erreur de transport de bibliothèque possède son propre type. Si vous appelez l'exécutable ssh comme sous-processus, il se peut que l'enveloppe ne connaisse que 255 ; conservez alors son stderr local et étiquetez honnêtement cette frontière.
L'achèvement signifie aussi que toute la sortie a été vidée. La documentation os/exec de Go avertit qu'il est incorrect d'appeler Wait avant la fin des lectures de StdoutPipe ou StderrPipe. Node.js trace une frontière similaire : son événement exit peut se produire alors que stdio reste ouvert, tandis que close vient après la fermeture des flux. Ces manuels décrivent des sous-processus locaux, mais la leçon s'applique directement à un auxiliaire SSH. Ne publiez le résultat final qu'après avoir connu la terminaison et vu les deux lecteurs atteindre leur état final.
L'expiration et l'annulation méritent des champs propres. Enregistrez qui a lancé l'annulation lorsque le système le sait, si un signal a été demandé et si le canal s'est réellement fermé. N'indiquez pas timed_out: true pour ensuite rejeter un rapport de sortie distant arrivé plus tard ; les deux événements peuvent compter pendant une enquête.
L'analyseur doit consommer stdout et garder le reste
Un analyseur propre à une commande doit recevoir les octets de stdout, la terminaison et les métadonnées du contenu. Il ne doit pas recevoir une transcription mélangée et deviner quelles lignes sont des diagnostics.
Supposons qu'un agent exécute une commande distante d'inventaire qui promet du JSON dans stdout. L'adaptateur doit d'abord vérifier que l'opération SSH a atteint une terminaison connue, appliquer ensuite la politique d'état de la commande, puis décoder et analyser stdout. Stderr reste attaché au résultat comme élément de contexte. Un avertissement n'entre pas dans l'analyseur JSON et une erreur d'analyse n'efface pas l'avertissement.
Renvoyez l'erreur d'analyse avec le résultat de la commande, pas à sa place. Une erreur utile peut indiquer que l'octet 418 de stdout est invalide tout en conservant stdout, stderr, l'état de sortie et les indicateurs de troncature d'origine. Cet ensemble permet à l'agent de corriger l'appel, de recommencer avec des paramètres régionaux stables ou de remettre les éléments exacts à une personne.
Évitez les API de commodité nommées CombinedOutput dans un chemin structuré pour agents. Le manuel Go décrit exactement ce que fait cette méthode : elle renvoie la sortie standard et l'erreur standard combinées. Elle convient à une commande de diagnostic ponctuelle et ne convient pas à un contrat réutilisable, car les étiquettes perdues ne peuvent plus être déduites.
Les commandes textuelles exigent elles aussi des choix propres à la commande. Un analyseur peut traiter stdout comme des enregistrements séparés par des retours à la ligne et présenter stderr comme un texte de diagnostic ordinaire. Un autre peut accepter un état zéro avec stdout vide comme résultat vide valide. Placez ces règles près de la définition de la commande, avec leurs tests, au lieu de les enfouir dans le transport.
La construction des instructions doit utiliser des champs structurés. Indiquez au modèle exit status: 2, fournissez stdout et stderr dans des blocs étiquetés séparément et précisez quand le contenu a été tronqué. Ne concaténez pas une sortie distante non fiable avec des instructions sans délimitation. La sortie peut contenir du texte qui ressemble à une instruction, alors traitez-la comme des données et échappez-la selon le format de conteneur employé.
Un agent ne doit pas décider de la réussite d'après un texte explicatif. Donnez-lui des champs lisibles par machine tels que termination.kind et exit_status, puis laissez le texte expliquer. Cela réduit le nombre de tokens et empêche un avertissement contenant le mot error de prendre le pas sur un état réussi.
Les vues d'audit ont besoin de deux rendus honnêtes
Le journal d'audit et la transcription destinée aux personnes n'ont pas le même rôle. Le journal conserve les octets et les métadonnées ; la transcription aide à les lire.
Une vue d'appel utile commence par une bande d'état : commande, identité de l'hôte, heures de début et de fin, type de terminaison, état de sortie ou signal, nombre d'octets et troncature. En dessous, proposez des onglets stdout et stderr séparés comme vues de référence. Un onglet combiné peut entrelacer les plages d'événements selon l'ordre observé, avec une étiquette de flux persistante sur chaque ligne.
Ne codez pas l'identité du flux uniquement par la couleur. Utilisez des étiquettes textuelles et fournissez une action de copie pour chaque flux d'origine. La copie de la vue combinée doit soit inclure des étiquettes explicites, soit avertir qu'il s'agit d'un rendu, car une sortie collée sans provenance recrée le problème initial.
Les longues lignes, les retours chariot et les codes de contrôle du terminal exigent un rendu prudent. Échappez les caractères de contrôle par défaut. Une barre de progression qui écrit \r à répétition ne doit pas écraser l'ancien contenu d'audit comme si la vue était un terminal. Proposez l'émulation de terminal uniquement comme vue dérivée facultative et gardez la représentation brute accessible.
La recherche doit renvoyer le flux, l'offset en octets et la séquence de l'événement avec chaque résultat. Le filtrage sur stderr ne doit pas modifier les numéros de séquence. Si le contenu est tronqué, placez un repère visible à l'endroit où des octets manquent et affichez le nombre enregistré. Ne collez jamais le préfixe et le suffixe comme s'ils étaient voisins dans la source.
Une chronologie peut placer la terminaison après le dernier fragment observé, mais seulement si la capture confirme que les deux lecteurs se sont fermés avant la finalisation. Si la connexion s'est interrompue, affichez le dernier événement de sortie, l'erreur de transport et un résultat distant inconnu. Réduire le tout à un badge rouge failed efface la différence entre un échec du programme et une perte d'éléments.
Sallyport fait passer les actions SSH par son auxiliaire sans état sp-ssh et enregistre chaque appel dans le journal Activity. Cette séparation doit donc se faire à la frontière du résultat de l'auxiliaire, avant qu'un agent ou une vue d'audit ne mette l'appel en forme. Le comportement utile du produit n'est pas une transcription astucieuse, mais la conservation d'assez d'éléments pour que les agents et les personnes puissent tirer leurs propres conclusions.
Testez les formes d'échec, pas un cas idéal
Une suite de tests d'analyseur doit faire varier indépendamment le découpage, le moment d'arrivée des flux, la terminaison, l'encodage et les limites de conservation. L'instantané d'une chaîne fusionnée teste surtout votre outil de mise en forme.
Commencez par une fausse source de canal qui émet des événements de protocole sous votre contrôle. Injectez une charge stdout découpée à chaque position possible. Faites de même avec un échantillon UTF-8 multioctet, une séquence ANSI et une dernière ligne sans \n. Les octets stockés doivent rester identiques quel que soit le découpage.
Entrelacez des événements stdout et stderr avec des numéros de séquence connus et vérifiez que les tampons séparés, les offsets de plage et la vue combinée concordent. Pour les implémentations à lecteurs indépendants, injectez des délais d'ordonnancement et n'affirmez que l'ordre des octets dans chaque flux et l'ordre d'observation du collecteur. Un test qui exige l'ordre du code source du producteur réclame une garantie que le système ne possède pas.
Couvrez les combinaisons de terminaison que les scénarios ordinaires oublient : zéro avec stderr, état non nul avec stderr vide, signal avec stdout partiel, fermeture de canal sans état, erreur de transport après la production de données par les deux flux, délai expiré suivi d'une fermeture tardive et annulation avant la confirmation du démarrage. Chacune doit produire un résultat structuré distinct.
Appliquez de petites limites à un flux à la fois. Vérifiez que la troncature de stdout ne marque pas stderr comme tronqué, que le nombre d'octets rejetés est correct, que les lecteurs continuent de vider les flux et que l'état final arrive tout de même. Remplissez ensuite les deux flux simultanément. Ce test détecte le blocage classique dans lequel le code vide entièrement stdout avant de commencer à lire stderr.
Les tests par propriétés conviennent bien aux invariants sur les octets. Générez des suites d'octets et des limites de fragments arbitraires, faites-les passer par le collecteur et exigez que la concaténation des plages conservées reproduise le contenu retenu. Générez les calendriers d'événements séparément des contenus afin que le test ne confonde jamais découpage et signification.
Enfin, testez chaque export. JSON doit conserver la différence entre null et zéro. La transcription textuelle doit étiqueter les flux. La suppression de données sensibles ne doit pas déplacer les offsets stockés sans enregistrer une correspondance, ou elle doit produire un artefact dérivé distinct. Un format d'audit mérite la confiance lorsqu'il laisse les échecs gênants visibles, au lieu de les normaliser en un récit propre mais faux.
Conservez les flux bruts, étiquetez l'ordre observé et attendez à la fois le vidage des flux et la terminaison avant de publier un résultat. Dès qu'une chaîne aplatie entre dans le message d'un agent ou dans un journal d'audit, les distinctions perdues ne peuvent plus être récupérées et chaque couche suivante doit deviner.
FAQ
Un contenu dans stderr doit-il faire échouer une commande SSH ?
Non. Stderr identifie les octets écrits sur le descripteur 2 ; il ne définit pas le résultat de la commande. Utilisez l'état de sortie ou le signal SSH comme résultat générique, puis laissez l'adaptateur décider si certains diagnostics modifient l'acceptation.
SSH peut-il conserver l'ordre exact de stdout et stderr ?
SSH peut conserver l'ordre des messages de canal observés par le client, mais cela ne prouve pas l'ordre des écritures du programme distant. Les tampons et les lecteurs indépendants modifient le moment où les octets apparaissent ; qualifiez donc la transcription fusionnée d'ordre observé.
Peut-on analyser stdout comme JSON lorsque stderr n'est pas vide ?
Oui, si le contrat de la commande stipule que stdout contient du JSON et si la terminaison respecte ce contrat. Analysez stdout seul et gardez stderr comme diagnostic ; ne transmettez jamais une transcription fusionnée à l'analyseur JSON.
Que faire si une commande SSH ne renvoie aucun état de sortie ?
Représentez le résultat comme inconnu au lieu de le traiter comme zéro. Conservez les deux flux et toute erreur de transport, car un canal fermé sans état ne prouve ni une réussite ni un échec de la commande.
L'agent doit-il recevoir les octets bruts ou le texte décodé ?
Le résultat durable doit conserver les octets ou un encodage sans perte. Vous pouvez proposer le texte décodé comme vue pratique, mais enregistrez les erreurs de décodage et ne remplacez jamais des octets invalides sans garder la source.
Pourquoi ne pas utiliser un pseudo-terminal pour toutes les commandes SSH ?
Un pseudo-terminal modifie la mise en tampon et supprime souvent la séparation nette entre stdout et stderr dont les analyseurs structurés ont besoin. Demandez-en un pour les commandes réellement interactives, pas pour l'automatisation qui attend une sortie lisible par machine.
Comment tronquer une sortie SSH volumineuse ?
Appliquez des limites distinctes à stdout et stderr, enregistrez les nombres d'octets conservés et rejetés, puis continuez à vider les deux flux. Rendez toute partie manquante visible afin qu'un préfixe et un suffixe ne paraissent pas adjacents.
Quand un résultat SSH est-il complet ?
Il est complet lorsque la terminaison est connue ou explicitement inconnue et que les deux lecteurs ont fini. La fin du processus ne suffit pas, car des données stdout ou stderr en tampon peuvent encore arriver.
Comment étiqueter les fragments d'une transcription SSH fusionnée ?
Chaque plage rendue doit porter une étiquette visible stdout ou stderr et sa séquence observée. Gardez les vues séparées comme références et incluez les étiquettes dans le texte combiné copié afin de préserver sa provenance.
Quelle est la meilleure façon de tester stdout et stderr ?
Générez des octets arbitraires, variez les limites et l'ordonnancement, puis vérifiez la reconstruction exacte de chaque flux. Ajoutez des signaux, des états absents, des sorties partielles, des délais, des erreurs de transport et des limites de troncature indépendantes.