8 min de leitura

Por que separar SSH stdout e stderr?

Separar SSH stdout e stderr oferece aos agentes dados confiáveis, diagnósticos úteis, ordem honesta e um resultado de saída inequívoco.

Por que separar SSH stdout e stderr?

O resultado de um comando SSH deve preservar stdout, stderr e a terminação como fatos distintos. Se você achatar tudo em uma única string, o agente deixa de distinguir dados de diagnóstico, o operador não consegue ver por que o comando falhou e uma visualização de auditoria pode apresentar uma sequência que nunca existiu.

A correção não é um delimitador mais bonito. Mantenha os dois fluxos de bytes separados, registre uma ordem limitada de observação quando o transporte expuser uma e represente de forma independente o status de saída, o sinal de saída, o tempo esgotado, o cancelamento e a falha de transporte. Esse modelo exige um pouco mais de trabalho no limite da captura e remove uma longa lista de erros de análise nas camadas seguintes.

O SSH já distingue os dois fluxos

O SSH transporta os dados comuns do canal e stderr em mensagens diferentes do protocolo. A RFC 4254 chama essas mensagens de SSH_MSG_CHANNEL_DATA e SSH_MSG_CHANNEL_EXTENDED_DATA; ela atribui o tipo 1 de dados estendidos a SSH_EXTENDED_DATA_STDERR. Uma biblioteca cliente que fornece leitores separados apenas expõe uma distinção que o protocolo preservou de propósito.

Essa distinção tem significado. Em geral, os programas escrevem resultados para consumo por máquina em stdout e diagnósticos em stderr. Um comando pode emitir JSON válido em stdout, imprimir um aviso em stderr e ainda retornar zero. Outro pode imprimir uma saída parcial, explicar uma falha em stderr e retornar um valor diferente de zero. Os bytes isolados não revelam qual caso ocorreu.

A combinação durante a captura elimina informações que nenhum analisador posterior consegue recuperar. Prefixos como [stderr] ajudam uma pessoa, mas alteram o conteúdo. Separadores de nova linha são piores: um fragmento pode terminar sem uma nova linha, dados binários podem conter qualquer byte e o separador acrescentado pode transformar dois fragmentos válidos em um documento inválido.

Trate cada fluxo como bytes até que um consumidor escolha uma política de decodificação. UTF-8 é comum, mas não é garantido pelo SSH. Mesmo ferramentas aparentemente textuais podem emitir sequências inválidas devido a diferenças de localidade, nomes de arquivo com bytes arbitrários ou uma escrita interrompida no meio de um caractere multibyte. Armazene os bytes brutos ou uma codificação sem perdas e ofereça o texto decodificado como uma visualização.

O fato do protocolo importa porque muda o ônus da prova. Se o tipo de resultado tem apenas output: string, esse tipo está mentindo sobre o que o SSH entregou. A formatação de conveniência deve ocorrer depois da captura, onde pode ser substituída sem reescrever o registro de auditoria.

A identidade do fluxo não indica gravidade

Stderr significa descritor de arquivo 2, não falha. Tratar cada byte de stderr como erro cria agentes barulhentos que repetem comandos bem-sucedidos, descartam stdout aproveitável ou pedem aprovação depois de avisos inofensivos.

Muitos programas conhecidos usam stderr para progresso, rastreamento detalhado, prompts e avisos. Um compilador pode reservar stdout para a saída gerada e informar o progresso em outro lugar. Um comando também pode falhar em silêncio com status diferente de zero. A relação fornece evidência útil, não uma regra booleana.

Mantenha pelo menos quatro conceitos separados:

  • stdout e stderr identificam de onde os bytes chegaram.
  • exit_status ou exit_signal descreve como o programa remoto terminou.
  • transport_error informa se a operação SSH em si foi concluída.
  • timed_out e cancelled descrevem uma intervenção local.

Isso evita um erro comum de analisador: transformar stderr != empty em success = false. Normalmente, sucesso deve significar que o comando iniciou, o canal foi concluído e o status remoto foi zero. A aplicação pode adotar uma regra mais rígida para um comando específico, mas ela deve ficar no adaptador do comando, não no executor SSH genérico.

O erro inverso causa o mesmo estrago. Alguns wrappers retornam apenas stdout quando há sucesso e substituem o resultado inteiro por uma exceção quando há falha. A exceção pode conter apenas um sufixo cortado de stderr, enquanto o stdout parcial desaparece. O agente recebe menos evidências justamente quando mais precisa delas.

Não sobrecarregue um único campo error com diagnósticos remotos, falhas de conexão, tempo esgotado e erros de análise. Essas condições exigem decisões de nova tentativa diferentes. Uma falha de DNS pode justificar uma repetição. Uma saída 2 causada por erro de uso normalmente não. JSON inválido em stdout exige a preservação dos bytes originais para que um desenvolvedor decida se o comando ou o analisador estava errado.

O contrato deve preservar fatos antes da interpretação

Um objeto de resultado durável contém as evidências brutas e torna explícitos os estados desconhecidos. Ele não deve obrigar cada consumidor a reconstituir os fatos a partir de uma transcrição formatada.

Este contrato é simples de propósito:

{
  "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"
}

Os dois objetos de fluxo são o conteúdo de referência. Cada evento aponta para uma faixa de bytes em vez de copiar o texto, permitindo que uma visualização monte uma transcrição sem duplicar o payload. seq significa apenas a ordem de observação na captura. Ele não afirma que as escritas remotas aconteceram exatamente nessa ordem.

O campo termination.kind deve abranger pelo menos exit, signal, timeout, cancelled, transport_error e unknown. Use campos anuláveis em vez de códigos mágicos. A ausência de status de saída SSH não significa zero, e um tempo esgotado local não equivale à saída 124, a menos que um shell ou o utilitário timeout realmente tenha produzido 124 no host remoto.

Inclua a informação de corte em cada fluxo. Um indicador global truncated não permite ao analisador saber se ainda há JSON completo em stdout ou se apenas o final de um stderr detalhado foi perdido. Registre as contagens de bytes capturados e descartados quando elas forem conhecidas. Se você mantiver apenas um prefixo e um sufixo, modele os dois como segmentos separados em vez de uni-los como se o meio nunca tivesse existido.

Marcas de tempo ajudam na latência e na investigação, mas não use o relógio de parede para ordenar fragmentos. Relógios podem saltar, e dois leitores concorrentes podem receber a mesma marca na resolução escolhida. Atribua o contador de sequência em um único ponto de serialização. Guarde separadamente uma duração monotônica se o ambiente de execução oferecer uma.

Versione o contrato antes que os clientes dependam dele. Acrescentar campos costuma ser seguro, mas mudar events.seq de ordem de chegada para ordem de exibição é uma quebra semântica, mesmo que a forma do JSON continue igual.

A ordem entre fluxos tem um limite rígido

Você pode preservar a ordem em que sua pilha SSH observou as mensagens do canal, mas normalmente não pode provar a ordem das escritas do programa remoto entre stdout e stderr. Essa limitação deve aparecer no modelo de dados e no texto da interface.

Dentro de um fluxo, os bytes permanecem ordenados. Entre dois fluxos, há buffers em várias camadas: o ambiente da linguagem remota, libc, pipes, o servidor SSH, pacotes de transporte, a biblioteca cliente e suas próprias tarefas de leitura. Stdout pode usar buffer em blocos quando não está ligado a um terminal, enquanto stderr pode ser descarregado antes. Uma escrita posterior em stderr pode, portanto, ficar visível antes de uma escrita anterior em stdout.

A RFC 4254 preserva a sequência das mensagens de canal enviadas pela implementação SSH. Isso é útil, e um callback da biblioteca que exponha essas mensagens pode atribuir uma sequência fiel de recebimento. Assim que a biblioteca divide os dados em leitores independentes de stdout e stderr, duas goroutines ou callbacks assíncronos disputam quem informa primeiro a disponibilidade. A ordem em que o escalonador os executa é uma observação da entrega local, não uma reconstrução da ordem do código-fonte remoto.

Este pequeno comando mostra por que um teste não deve exigir uma única transcrição combinada universal:

sh -c 'printf "out-1\n"; printf "err-1\n" >&2; printf "out-2\n"; printf "err-2\n" >&2'

Um terminal costuma exibir a ordem aparente do código. Se você redirecionar os dois descritores para um arquivo com >all.log 2>&1, o shell apontará ambos para o mesmo destino, fornecendo àquele processo um único caminho de escrita gerenciado pelo kernel. Se forem capturados por pipes separados, o observador poderá receber os fragmentos em outra ordem. Acrescente um ambiente de linguagem com buffer e a diferença aumenta.

Se a cronologia exata entre fluxos for obrigatória, mude o contrato do produtor. Faça o programa remoto escrever registros estruturados com seu próprio número de sequência em um único fluxo, ou direcione os dois descritores para o mesmo destino remoto antes que o SSH os veja. Isso oferece uma ordem definida ao custo de abandonar fluxos independentes no produtor. Um cliente SSH genérico não consegue fabricar o fato ausente depois.

O texto de auditoria deve dizer sequência observada, não sequência de execução. A precisão não é excesso jurídico. Ela impede que uma pessoa investigando interprete o momento do escalonamento como relação de causa e efeito.

Os limites dos fragmentos vêm do transporte

Separe as sessões das chamadas
Sessions acompanha a execução do agente e Activity registra cada comando solicitado ao Sallyport.

Um callback de leitura não corresponde a uma linha, um registro ou uma chamada remota a write. Analisadores que partem dessa premissa funcionam nos testes e falham sob carga.

Uma escrita pode chegar em vários fragmentos. Várias escritas podem chegar em um só. Um ponto de código UTF-8, uma sequência de escape ANSI ou um token JSON pode atravessar um limite. O mesmo comando pode produzir fragmentação diferente na execução seguinte sem mudar a saída.

Construa a camada de captura em torno de operações de anexação de bytes. Para cada fluxo, acrescente o fragmento ao buffer ou arquivo temporário e registre o offset e o comprimento resultantes. Se a biblioteca expuser mensagens em série, atribua seq ali. Se fornecer leitores independentes, envie avisos de fragmento a um coletor único e documente que a sequência reflete o recebimento pelo coletor.

A separação em linhas pertence a uma visualização derivada. Mantenha um decodificador incremental e um buffer de linha incompleta para cada fluxo. Nunca compartilhe um buffer de linhas entre stdout e stderr, pois um fragmento de stdout sem término seguido de uma linha de stderr não deve virar uma linha sintética. Quando o fluxo fechar, exponha a última linha parcial em vez de descartá-la silenciosamente.

A análise de JSON normalmente deve esperar até stdout chegar ao fim e a terminação do comando ser conhecida. Um protocolo JSON em streaming é diferente: ele precisa de enquadramento explícito, como JSON delimitado por novas linhas, um prefixo de comprimento ou uma gramática incremental documentada. Adivinhar limites de registros a partir dos fragmentos não é streaming, é uma corrida.

A saída binária também precisa de um caminho explícito. Base64 dentro de JSON é simples e portável, embora aumente o tamanho. Uma referência para um blob pode servir para resultados grandes se o sistema de auditoria garantir retenção e integridade. Não decodifique com caracteres de substituição e descarte o original. A substituição esconde se a corrupção veio da ferramenta remota, do adaptador de transporte ou da visualização.

Os limites devem ser aplicados durante a leitura, não depois de carregar tudo na memória. Continue drenando os dois fluxos mesmo se um ultrapassar seu limite de retenção, ou o processo remoto pode bloquear em um pipe cheio. Armazene o prefixo, o sufixo ou o arquivo externo permitido, conte os bytes descartados e continue lendo até o fechamento ou cancelamento.

Um pseudoterminal troca estrutura por comportamento

Não solicite um pseudoterminal para um comando cujo stdout será analisado. Um PTY é útil em uma sessão humana, mas altera o ambiente do programa e costuma enviar stdout e stderr pelo mesmo dispositivo de terminal antes que o cliente SSH consiga preservar a identidade de cada um.

Os programas verificam se um descritor aponta para um terminal. Eles podem ativar cores, desenhar progresso com retornos de carro, quebrar linhas na largura informada, pedir entrada ou mudar de buffer em blocos para buffer por linha. Portanto, os bytes capturados com PTY podem ser diferentes dos produzidos pelo mesmo comando sem PTY. Isso é uma mudança observável de comportamento, não uma opção de apresentação.

As opções do cliente OpenSSH refletem a distinção: -T desativa a alocação de pseudoterminal, enquanto -t a solicita e repetir -t pode forçá-la. A automação deve usar execução sem PTY por padrão. Só solicite um quando o programa remoto exigir semântica de terminal e o contrato do resultado informar de forma explícita que a separação dos fluxos não está disponível.

Um PTY não torna a ordem mais verdadeira. Ele pode fornecer um único fluxo de bytes do terminal, então a ordem exibida fica definida nesse limite, mas o programa e suas bibliotecas podem usar buffers diferentes ao detectar o terminal. Você trocou evidências separadas por comportamento interativo; não descobriu a cronologia de uma execução sem PTY.

Essa distinção explica uma classe persistente de erros. Uma pessoa testa o comando no shell e vê um progresso limpo, colorido e em ordem razoável. O agente executa o mesmo texto sem PTY, stdout passa a usar buffer em blocos, stderr aparece primeiro e o analisador recebe mais tarde a saída para máquina sem códigos de controle. Alguém então força um PTY para aproximar a transcrição do teste manual, e a análise de JSON começa a falhar porque códigos de cor ou prompts entram no fluxo.

Trate a execução interativa e a estruturada como modos diferentes da API. O modo estruturado deve prometer fluxos separados e comportamento de captura estável sem emulação de terminal. O modo interativo deve retornar uma transcrição do terminal, suas dimensões e uma indicação explícita de que a identidade original de stdout e stderr não foi preservada. Um pty: true escondido nas opções da solicitação não basta se a resposta parece idêntica a um resultado estruturado.

Arquivos remotos de inicialização acrescentam outra complicação. A RFC 4254 avisa que a inicialização do shell pode produzir saída indevida ao iniciar um subsistema e recomenda um marcador reconhecível para protocolos que precisem distingui-la. A mesma lição se aplica aos adaptadores de comando: invoque o caminho executável mais direto que você controla, evite shells interativos desnecessários e trate bytes iniciais inesperados como evidência, em vez de remover silenciosamente qualquer coisa que pareça um banner.

Se um comando realmente precisar de prompt de senha ou controle de terminal, não finja que sua transcrição está pronta para análise. Dê ao agente uma ferramenta de interação especializada, com entradas limitadas e uma transcrição concebida para a semântica do terminal. Manter esse caminho separado protege a garantia mais simples de que ações SSH comuns retornam stdout fiel, stderr fiel e um resultado de terminação.

O status de saída faz parte do resultado

Coloque o SSH atrás de um auxiliar
O Sallyport executa comandos remotos pelo sp-ssh e devolve o resultado ao agente.

A RFC 4254 define uma solicitação de canal exit-status e uma forma separada exit-signal. Ela recomenda retornar o status, mas também permite que o cliente o ignore. Sua API precisa, portanto, de um resultado desconhecido explícito em vez de presumir sucesso quando nenhum status chegou.

Um status zero geralmente indica sucesso, não certeza. A RFC 4254 usa essa formulação qualificada porque as convenções dos comandos ficam acima do transporte. Ainda assim, o status é o principal sinal genérico disponível. Preserve o valor sem sinal fornecido pelo protocolo antes de convertê-lo para as convenções de processo da linguagem hospedeira.

Terminação por sinal não é um status de saída negativo. Armazene separadamente o nome do sinal, o indicador de despejo de memória quando fornecido e a mensagem explicativa remota. Se um consumidor quiser exibir um número semelhante ao do shell, como 128 mais o valor do sinal, ele pode derivá-lo para apresentação. O registro de auditoria deve manter os fatos do SSH.

Diferencie estes resultados no código e na interface:

  • O comando remoto retornou um status.
  • O lado remoto informou terminação por sinal.
  • O canal fechou sem nenhum dos dois informes.
  • O cliente falhou antes da confirmação de início do comando.
  • A conexão falhou depois que uma saída parcial chegou.

O quarto caso não deve se disfarçar como saída remota 255 só porque o cliente OpenSSH de linha de comando costuma usar 255 para os próprios erros. Um erro de transporte da biblioteca tem seu próprio tipo. Se você invocar o executável ssh como subprocesso, talvez 255 seja tudo que o wrapper saiba; nesse caso, preserve o stderr local e identifique esse limite com honestidade.

Conclusão também significa que toda a saída foi drenada. A documentação de os/exec do Go alerta que chamar Wait antes do fim das leituras de StdoutPipe ou StderrPipe é incorreto. O Node.js traça um limite parecido: seu evento exit pode ocorrer enquanto stdio continua aberto, enquanto close vem depois do fechamento dos fluxos. Esses manuais descrevem subprocessos locais, mas a lição de projeto se aplica diretamente a um auxiliar SSH. Publique o resultado final somente quando a terminação for conhecida e os dois leitores de saída tiverem chegado ao estado final.

Tempo esgotado e cancelamento merecem campos próprios. Registre quem iniciou o cancelamento quando o sistema souber, se um sinal foi solicitado e se o canal realmente fechou. Não declare timed_out: true e descarte um relatório remoto de saída que chegue depois; os dois eventos podem importar em uma investigação.

O analisador deve consumir stdout e guardar o restante

Um analisador específico de comando deve receber os bytes de stdout, a terminação e os metadados do conteúdo. Ele não deve receber uma transcrição misturada e adivinhar quais linhas são diagnósticos.

Suponha que um agente execute um comando remoto de inventário que prometa JSON em stdout. Primeiro, o adaptador deve verificar se a operação SSH chegou a uma terminação conhecida, depois aplicar a política de status do comando e, por fim, decodificar e analisar stdout. Stderr permanece anexado ao resultado como evidência de apoio. Um aviso não entra no analisador JSON, e uma falha de análise não apaga o aviso.

Retorne a falha de análise junto com o resultado do comando, não no lugar dele. Um erro útil pode informar que o byte 418 de stdout era inválido e ainda preservar stdout, stderr, o status de saída e os indicadores de corte originais. Com esse conjunto, o agente pode decidir se corrige a invocação, tenta novamente com uma localidade estável ou entrega a evidência exata a uma pessoa.

Evite APIs de conveniência chamadas CombinedOutput em um caminho estruturado para agentes. O manual do Go descreve exatamente o que esse método faz: retorna a saída padrão e o erro padrão combinados. Ele é prático para um comando pontual de diagnóstico e errado para um contrato reutilizável, pois os rótulos perdidos não podem ser inferidos depois.

Comandos de texto também precisam de escolhas específicas. Um analisador pode tratar stdout como registros delimitados por novas linhas e apresentar stderr como texto comum de diagnóstico. Outro pode aceitar status zero com stdout vazio como resultado vazio válido. Coloque essas regras junto à definição do comando, com testes, em vez de embuti-las no transporte.

A construção do prompt deve usar campos estruturados. Informe ao modelo exit status: 2, forneça stdout e stderr em blocos rotulados separadamente e indique quando o conteúdo foi cortado. Não concatene uma saída remota não confiável a instruções sem limites. A saída pode conter texto parecido com um prompt, então trate-a como dados e aplique o escape adequado ao formato do contêiner usado.

Um agente não deve decidir o sucesso a partir de texto explicativo. Entregue campos para máquina, como termination.kind e exit_status, e deixe o texto explicar. Isso reduz o uso de tokens e impede que um aviso contendo a palavra error se sobreponha a um status bem-sucedido.

As visualizações de auditoria precisam de dois retratos honestos

Bloqueie toda ação remota
Quando o cofre protegido por hardware está bloqueado, o Sallyport nega toda ação SSH.

O registro de auditoria e a transcrição para pessoas têm funções diferentes. O registro preserva bytes e metadados; a transcrição ajuda alguém a lê-los.

Uma visualização útil começa com uma faixa de status: comando, identidade do host, horários inicial e final, tipo de terminação, status de saída ou sinal, contagens de bytes e corte. Abaixo, ofereça abas separadas para stdout e stderr como visualizações de referência. Uma aba combinada pode intercalar faixas de eventos pela sequência observada, com um rótulo de fluxo persistente em cada linha.

Não codifique a identidade do fluxo apenas por cor. Use rótulos de texto e ofereça uma ação de cópia para cada fluxo original. A cópia da visualização combinada deve incluir rótulos explícitos ou avisar que se trata de uma representação, pois uma saída colada sem origem recria o problema inicial.

Linhas longas, retornos de carro e códigos de controle do terminal exigem uma renderização cuidadosa. Escape caracteres de controle por padrão. Uma barra de progresso que escreve \r repetidamente não deve sobrescrever conteúdo antigo de auditoria como se a visualização fosse um terminal. Ofereça emulação de terminal apenas como visualização derivada opcional e mantenha a representação bruta acessível.

A busca deve retornar o fluxo, o offset de bytes e a sequência do evento com cada ocorrência. Filtrar por stderr não deve mudar os números da sequência. Se o conteúdo estiver cortado, coloque um marcador visível onde faltam bytes e mostre a contagem registrada. Nunca encoste o prefixo no sufixo como se fossem adjacentes na origem.

Uma linha do tempo pode colocar a terminação depois do último fragmento observado, mas apenas quando a captura confirmar que ambos os leitores fecharam antes da finalização. Se a conexão caiu, mostre o último evento de saída, a falha de transporte e um resultado remoto desconhecido. Reduzir tudo a um selo vermelho failed apaga a diferença entre falha do programa e perda de evidência.

O Sallyport encaminha ações SSH por meio do auxiliar sem estado sp-ssh e registra chamadas individuais no diário Activity, portanto essa separação deve ocorrer no limite do resultado do auxiliar, antes que um agente ou uma visualização de auditoria formate a chamada. O comportamento útil do produto não é uma transcrição engenhosa; é preservar evidências suficientes para que agentes e pessoas cheguem às próprias conclusões.

Teste as formas de falha, não um caso feliz

Uma suíte de testes do analisador deve variar de forma independente a fragmentação, o momento de cada fluxo, a terminação, a codificação e os limites de retenção. Um snapshot de uma string combinada testa principalmente o formatador.

Comece com uma fonte falsa de canal que emita eventos no nível do protocolo sob seu controle. Forneça um payload de stdout dividido em todos os pontos possíveis. Repita com uma amostra UTF-8 multibyte, uma sequência ANSI e uma linha final sem \n. Os bytes armazenados devem permanecer idênticos em toda divisão.

Intercale eventos de stdout e stderr com números de sequência conhecidos e confirme que buffers separados, offsets de faixa e visualização combinada concordam. Em implementações com leitores independentes, injete atrasos de escalonamento e verifique apenas a ordem dos bytes em cada fluxo e a ordem observada pelo coletor. Um teste que insiste na ordem do código-fonte do produtor exige uma garantia que o sistema não oferece.

Cubra combinações de terminação que os dados comuns de teste esquecem: zero com stderr, valor não zero com stderr vazio, sinal com stdout parcial, fechamento do canal sem status, falha de transporte depois de ambos os fluxos produzirem dados, tempo esgotado seguido de fechamento tardio e cancelamento antes da confirmação do início. Cada uma deve produzir um resultado estruturado diferente.

Aplique limites pequenos a um fluxo de cada vez. Verifique que o corte de stdout não marca stderr como cortado, que as contagens de bytes descartados estão corretas, que os leitores continuam drenando e que o status final ainda chega. Depois encha os dois fluxos ao mesmo tempo. Isso detecta o deadlock clássico em que o código drena stdout por completo antes de começar a ler stderr.

Testes baseados em propriedades funcionam bem para invariantes de bytes. Gere sequências arbitrárias de bytes e limites de fragmentos, passe tudo pelo coletor e exija que a concatenação das faixas retidas reproduza o conteúdo preservado do fluxo. Gere os agendamentos de eventos separadamente do conteúdo para que o teste nunca confunda fragmentação com significado.

Por fim, teste toda exportação. O JSON deve preservar a diferença entre null e zero. A transcrição de texto deve rotular os fluxos. A remoção de dados sensíveis não deve deslocar offsets armazenados sem registrar um mapeamento, ou deve produzir um artefato derivado separado. Um formato de auditoria merece confiança quando falhas incômodas continuam incômodas e visíveis, em vez de serem normalizadas em uma história limpa e falsa.

Mantenha os fluxos brutos, identifique a ordem observada e espere tanto a drenagem quanto a terminação antes de publicar o resultado. Depois que uma string achatada entra na mensagem do agente ou no log de auditoria, as distinções perdidas não podem ser recuperadas e cada camada seguinte precisa adivinhar.

FAQ

Uma saída em stderr deve fazer o comando SSH falhar?

Não. Stderr identifica os bytes escritos no descritor 2; ele não define o resultado do comando. Use o status ou o sinal SSH como resultado genérico e deixe o adaptador específico decidir se algum diagnóstico muda a aceitação.

O SSH consegue preservar a ordem exata de stdout e stderr?

O SSH pode preservar a ordem das mensagens de canal observadas pelo cliente, mas isso não prova a ordem das escritas do programa remoto. Buffers e leitores independentes mudam quando os bytes aparecem, portanto rotule a transcrição combinada como ordem observada.

É seguro analisar stdout como JSON quando stderr não está vazio?

Sim, se o contrato do comando disser que stdout contém JSON e a terminação cumprir esse contrato. Analise apenas stdout e preserve stderr como diagnóstico; nunca entregue uma transcrição combinada ao analisador JSON.

O que fazer quando um comando SSH não retorna status de saída?

Represente o resultado como desconhecido em vez de tratá-lo como zero. Preserve os dois fluxos e qualquer erro de transporte, pois um canal fechado sem status não prova sucesso nem falha do comando.

O agente deve receber bytes brutos ou texto decodificado?

O resultado durável deve manter os bytes ou uma codificação sem perdas. Você também pode oferecer texto decodificado como visualização conveniente, mas registre erros de decodificação e nunca substitua bytes inválidos sem guardar a origem.

Por que não usar um pseudoterminal em todo comando SSH?

Um pseudoterminal altera os buffers e muitas vezes elimina a separação clara entre stdout e stderr exigida pelos analisadores estruturados. Solicite um para comandos realmente interativos, não para automação que espera saída legível por máquina.

Como uma saída SSH grande deve ser cortada?

Aplique limites separados a stdout e stderr, registre bytes mantidos e descartados e continue drenando os dois fluxos. Torne qualquer trecho ausente visível para que prefixo e sufixo não pareçam adjacentes.

Quando um resultado SSH está completo?

Ele está completo quando a terminação é conhecida ou marcada como desconhecida e ambos os leitores acabaram. O fim do processo não basta, pois stdout ou stderr armazenado em buffer ainda pode chegar.

Como uma transcrição SSH combinada deve rotular fragmentos?

Cada faixa exibida deve ter um rótulo visível de stdout ou stderr e sua sequência observada. Mantenha as visualizações separadas como referência e inclua rótulos no texto combinado copiado para preservar a origem.

Qual é a melhor forma de testar stdout e stderr?

Gere bytes arbitrários, varie limites e escalonamento e verifique a reconstrução exata de cada fluxo. Inclua sinais, status ausente, saída parcial, tempo esgotado, falhas de transporte e limites independentes de corte.

Sallyport

O Sallyport executa chamadas de API e comandos SSH pelo seu agente de IA. As chaves ficam em um cofre local no seu Mac; você aprova cada execução e toda ação vai para um registro selado.

© 2026 Sallyport · Código aberto sob Apache-2.0 · Oleg Sotnikov