Skip to content

fix(cli): NO_COLOR/non-TTY output, golden-path help, heartbeat 403 re-enroll#116

Open
secxena wants to merge 1 commit into
stagingfrom
fix/ux-cli
Open

fix(cli): NO_COLOR/non-TTY output, golden-path help, heartbeat 403 re-enroll#116
secxena wants to merge 1 commit into
stagingfrom
fix/ux-cli

Conversation

@secxena

@secxena secxena commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

UX/usability fixes from the CLI audit (soth edge proxy, crates/soth-cli).

1. NO_COLOR / non-TTY output

style::{success,warning,info,kv,highlight,success_prefix} emitted unconditional ANSI even into pipes/CI/NO_COLOR. They now gate through the existing logging::use_ansi_colors() (honors NO_COLOR env + std::io::IsTerminal on stdout) and plain-print when color is off. Fleet-wide payoff for piped/CI output.

2. Golden path in help

  • Added a GETTING STARTED after_help block to the top-level command: standalone (setup-ca -> up) and cloud (setup-ca -> enroll -> up) paths.
  • Reordered subcommands so the one-shot soth up bootstrap is listed first (was buried after start/stop).
  • soth init numbered next-steps now include the enroll step so cloud users don't dead-end at bundle bootstrap.

3. Heartbeat 403 visibility + re-enroll escape hatch

The known org-mismatch pain: a sync heartbeat 403 was only a daemon warn!, soth status showed "Last heartbeat: never" with no cause, and soth doctor had no cloud/auth check.

  • New soth_sync::heartbeat_rejection sidecar (~/.soth/run/heartbeat_rejection.json): persisted on any non-2xx heartbeat (status + message + endpoint), cleared on the next accepted heartbeat. Best-effort — never aborts the heartbeat loop.
  • soth status: SYNC section now shows Heartbeat: REJECTED (HTTP 403, ...) with a soth enroll --new-device-id <token> fix line, and folds the rejection into the degraded/health verdict.
  • soth doctor: new CLOUD section + an error finding (cloud_heartbeat_rejected) with the re-enroll remediation.
  • soth enroll --new-device-id: regenerates the client device id before the exchange (default reuses the stale, cloud-rejected id, reproducing the same 403).

Verification

  • cargo build -p soth-cli — green.
  • cargo test -p soth-cli — 127 + 9 passed, 0 failed.
  • cargo test -p soth-sync --lib heartbeat_rejection — passed.
  • New tests: device-id regeneration (cli_config), cloud heartbeat findings (doctor), rejection sidecar roundtrip (soth-sync).

🤖 Generated with Claude Code

… re-enroll

UX/usability fixes from the CLI audit:

1. NO_COLOR / non-TTY: style::{success,warning,info,kv,highlight,
   success_prefix} now gate ANSI through logging::use_ansi_colors()
   (NO_COLOR env + IsTerminal on stdout), plain-printing into pipes/CI.

2. Golden path in help: add a "GETTING STARTED" after_help block to the
   top-level command (standalone: setup-ca -> up; cloud: setup-ca ->
   enroll -> up) and reorder subcommands so the one-shot `up` bootstrap
   is listed first. `soth init` next-steps now include the `enroll` step
   so cloud users don't dead-end at bundle bootstrap.

3. Heartbeat 403 visibility + re-enroll escape hatch: persist the last
   heartbeat rejection (status + message + endpoint) to a run-dir sidecar
   (~/.soth/run/heartbeat_rejection.json), cleared on the next accepted
   heartbeat. Surface it in `soth status` (SYNC section + degraded health)
   and `soth doctor` (new CLOUD section + error finding), each with a
   re-enroll remediation. Add `soth enroll --new-device-id` to regenerate
   the stale device_id that reproduces the org-mismatch 403.

Tests: device-id regeneration (cli_config), cloud heartbeat findings
(doctor), rejection sidecar roundtrip (soth-sync). cargo build -p
soth-cli and cargo test -p soth-cli green (127 + 9 passed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant