Skip to content

fix(ui): make single-host output byte-transparent and honor NO_COLOR #283

Description

@inureyes

Part of the OpenSSH drop-in compatibility epic.

Problem

bssh decorates its output in ways that are correct for a multi-node cluster run and wrong for a single-host ssh substitution. A drop-in replacement has to be byte-transparent, because the callers are rsync -e, git, Ansible, shell pipelines and CI, none of which tolerate an extra banner or an escape sequence.

Observed on bssh 2.4.3 with stderr redirected to a pipe, so no terminal is attached:

^[[1m^[[36m>^[[39m^[[0m ^[[36mExecuting^[[39m on ^[[1m1^[[0m node:
^[[2m  true^[[0m
...
^[[2m========================================^[[0m
^[[1m Summary: ^[[1m1^[[0m nodes . ^[[1m^[[31m1^[[39m^[[0m ^[[31mfailed^[[39m ^[[0m

Four distinct defects:

  1. A banner (Executing on 1 node:, the echoed command) and a summary rule are emitted even for a single destination. OpenSSH emits nothing.
  2. ANSI escape sequences are written when the stream is not a terminal.
  3. NO_COLOR=1 does not suppress them. Verified: identical output with and without the variable set.
  4. -V writes the version to stdout. OpenSSH writes it to stderr and stdout stays empty, so ssh -V >/dev/null still shows the version and a caller capturing stdout gets nothing extra.

There is a fifth, smaller one: the tracing subscriber prints timestamped WARN bssh::ssh::ssh_config::parser::options: Unknown SSH config option '...' lines to stderr on every invocation when a config contains a keyword bssh does not know. OpenSSH accepts ChallengeResponseAuthentication silently as a deprecated alias, so a config that is quiet under ssh is noisy under bssh.

Scope

  • In single-destination mode, suppress the banner, the per-node prefix and the summary entirely. Both stdout and stderr must carry only what the remote command and the SSH layer produce.
  • Gate all styling on the destination stream being a terminal, checked per stream rather than globally.
  • Honor NO_COLOR (any non-empty value), TERM=dumb, and add an explicit --color=auto|always|never for callers that want to force either way.
  • Move -V output to stderr and match OpenSSH's single-line shape.
  • Keep the deprecated-alias set OpenSSH accepts silent, and route genuine unknown-keyword warnings through the normal diagnostic path rather than the tracing subscriber, so they are not timestamped and module-tagged.

Relationship to the rest of the epic

This is what decides whether rsync -e bssh and git work at all, independent of the regress score. Several suite tests compare bytes exactly (transfer moves 10MB, stderr-data, stderr-after-eof, broken-pipe, yes-head), so they gate on it too, but the user-visible value lands before any of them pass.

Acceptance criteria

  • A single-destination run produces byte-identical stdout to OpenSSH for the same command, verified against a captured reference.
  • No ANSI sequences appear on a non-terminal stream, checked per stream.
  • NO_COLOR, TERM=dumb and --color=never each suppress styling; --color=always forces it.
  • bssh -V writes to stderr and leaves stdout empty.
  • A config containing ChallengeResponseAuthentication produces no output.
  • The regress tests transfer, stderr-data, stderr-after-eof and yes-head pass in the harness.

Part of #275

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions