Skip to content

Terminal Detection Guide

RAprogramm edited this page Oct 20, 2025 · 1 revision

Terminal Detection Guide

Understanding how masterror automatically detects terminal capabilities

This guide explains the intelligent terminal detection system that determines when to apply colored output.

Table of Contents

Detection Strategy

Masterror uses a multi-layered approach to detect whether colored output should be enabled:

┌─────────────────────────┐
│  Check NO_COLOR env     │──► Set? → No colors
└───────────┬─────────────┘
            │ Not set
            ▼
┌─────────────────────────┐
│  Check TERM env         │──► "dumb"? → No colors
└───────────┬─────────────┘
            │ Not dumb
            ▼
┌─────────────────────────┐
│  Check if stderr is TTY │──► Not TTY? → No colors
└───────────┬─────────────┘
            │ Is TTY
            ▼
┌─────────────────────────┐
│  Apply colors ✓         │
└─────────────────────────┘

This ensures colors are only shown when appropriate and never interfere with piped output or CI systems.

TTY Detection

What is a TTY?

A TTY (Teletypewriter) is a terminal device that supports interactive input/output. Modern terminal emulators emulate TTY behavior.

How Detection Works

Masterror uses owo-colors' if_supports_color with Stream::Stderr to detect TTY:

use owo_colors::{OwoColorize, Stream};

let text = "Error";
let colored = text.if_supports_color(Stream::Stderr, |t| t.red());

Under the hood:

  • On Unix: Calls isatty(2) syscall
  • On Windows: Checks console mode via GetConsoleMode
  • No-std: Always returns false

When is stderr a TTY?

Scenario TTY? Colors? Reason
cargo run ✅ Yes ✅ Yes Interactive terminal
cargo run | cat ❌ No ❌ No Piped to another process
cargo run > log.txt ❌ No ❌ No Redirected to file
SSH session ✅ Yes ✅ Yes SSH allocates pseudo-TTY
Docker with -it ✅ Yes ✅ Yes Interactive + TTY flags
Docker without -it ❌ No ❌ No No TTY allocated
CI/CD (GitHub Actions) ❌ No ❌ No Typically no TTY

Example: Piped Output

# Terminal - colors enabled
$ cargo run --example colored_cli --features colored
Error: Internal server error    # ← Red colored

# Piped - colors disabled
$ cargo run --example colored_cli --features colored | cat
Error: Internal server error    # ← Plain text

The same binary automatically adjusts based on whether stderr is connected to a TTY.

Environment Variables

NO_COLOR

The NO_COLOR standard (https://no-color.org/) is a universal convention for disabling colored output.

Usage:

NO_COLOR=1 cargo run --example colored_cli --features colored

Detection:

// Checked first, before TTY detection
if std::env::var_os("NO_COLOR").is_some() {
    // Disable colors regardless of TTY status
}

Note: The value doesn't matter - presence alone disables colors:

  • NO_COLOR=1
  • NO_COLOR=0 ✅ (still disables!)
  • NO_COLOR= ✅ (even empty)
  • Unset ❌ (colors enabled if TTY)

TERM

The TERM environment variable indicates terminal type. Masterror checks for TERM=dumb.

Common values:

  • xterm-256color - Modern terminals, colors enabled
  • screen - GNU Screen, colors enabled
  • dumb - No ANSI support, colors disabled

CI/CD examples:

# GitHub Actions (typically)
TERM=dumb

# GitLab CI
TERM=dumb

# Some older CI systems
TERM=xterm  # Colors may be enabled

Override for CI:

# .github/workflows/ci.yml
- name: Run tests
  run: cargo test --features colored
  env:
    NO_COLOR: "1"  # Explicitly disable colors

COLORTERM

Some terminals set COLORTERM=truecolor or COLORTERM=24bit. Masterror (via owo-colors) respects this for enhanced color support, but it's not required for basic ANSI colors.

Platform-Specific Behavior

Linux/Unix

Uses standard POSIX isatty() function:

#include <unistd.h>
int isatty(int fd);  // Returns 1 if fd is a TTY

File descriptors:

  • 0 = stdin
  • 1 = stdout
  • 2 = stderr ← Used for error output

macOS

Identical to Linux - uses POSIX isatty().

Terminal.app and iTerm2 both fully support ANSI colors.

Windows 10+

Uses Windows Console API:

#include <windows.h>
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
DWORD mode;
GetConsoleMode(hConsole, &mode);  // Check console mode

Windows Terminal (default in Windows 11) has native ANSI support.

Legacy cmd.exe and PowerShell 5.1 support colors via Console API.

Windows 7/8

Older Windows versions may not support ANSI escape codes. Masterror automatically disables colors when:

  • Console mode check fails
  • No ANSI processing mode available

Graceful degradation ensures the program still works, just without colors.

SSH Sessions

SSH allocates a pseudo-TTY by default for interactive sessions:

# Allocates PTY - colors enabled
ssh user@host cargo run --example colored_cli --features colored

# Force no PTY - colors disabled
ssh -T user@host cargo run --example colored_cli --features colored

Docker Containers

# With TTY + interactive - colors enabled
docker run -it my-app

# Without TTY - colors disabled
docker run my-app

# Explicit TTY - colors enabled
docker run -t my-app

Override Mechanisms

Force Enable Colors

Currently not supported to prevent interfering with piped output. Use NO_COLOR= (unset) to allow automatic detection.

Force Disable Colors

Multiple options:

# Option 1: NO_COLOR environment variable
NO_COLOR=1 cargo run

# Option 2: TERM=dumb
TERM=dumb cargo run

# Option 3: Pipe output
cargo run | cat

# Option 4: Redirect stderr
cargo run 2>&1 | tee log.txt

Per-Application Configuration

If you need programmatic control, you can:

  1. Build without the feature:

    masterror = "0.24"  # No colored feature
  2. Check environment before running:

    if std::env::var("MY_APP_NO_COLOR").is_ok() {
        std::env::set_var("NO_COLOR", "1");
    }

Testing

Unit Tests

Terminal detection is tested automatically in CI:

#[test]
fn colors_disabled_in_ci() {
    // CI environments typically don't have TTY
    // Colors should be disabled
    let err = AppError::internal("test");
    let output = format!("{}", err);

    // Output should not contain ANSI escape codes
    assert!(!output.contains("\x1b["));
}

Manual Testing

Test colored output behavior:

# 1. Interactive terminal (should show colors)
cargo run --example colored_cli --features colored

# 2. Piped (should NOT show colors)
cargo run --example colored_cli --features colored | cat

# 3. NO_COLOR (should NOT show colors)
NO_COLOR=1 cargo run --example colored_cli --features colored

# 4. TERM=dumb (should NOT show colors)
TERM=dumb cargo run --example colored_cli --features colored

# 5. Redirected (should NOT show colors)
cargo run --example colored_cli --features colored 2>log.txt
cat log.txt

Detecting ANSI Codes in Output

# If colors are enabled, you'll see escape codes
cargo run --example colored_cli --features colored 2>&1 | od -c | grep ESC

# Example escape code for red: \033[31m or \x1b[31m

CI Testing

Example GitHub Actions workflow:

name: Test colored output

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions-rs/toolchain@v1
        with:
          toolchain: stable

      - name: Test with colors disabled (CI default)
        run: cargo test --features colored

      - name: Test with NO_COLOR
        run: NO_COLOR=1 cargo run --example colored_cli --features colored

      - name: Verify no ANSI codes in CI output
        run: |
          cargo run --example colored_cli --features colored 2>&1 | \
          if grep -q $'\x1b\['; then
            echo "ERROR: Found ANSI codes in non-TTY output"
            exit 1
          fi

Troubleshooting

Problem: Colors not showing in my terminal

Solution: Check:

  1. NO_COLOR environment variable: echo $NO_COLOR
  2. TERM variable: echo $TERM
  3. Run directly (not piped): cargo run
  4. stderr is connected: Try cargo run 2>&1

See Troubleshooting Guide for more solutions.


Related Pages:


Previous: Colored Terminal Output | Next: Color Scheme Reference

Clone this wiki locally