BloopAI/vibe-kanban · error

UTF-8 after stripping ANSI

Error message

UTF-8 after stripping ANSI

What it means

The BACKEND_PORT/PORT env var is sanitized by stripping ANSI escape codes before parsing as u16. If, after stripping, the bytes are still not valid UTF-8, String::from_utf8 fails and the .expect("UTF-8 after stripping ANSI") panics with this message. It is a defensive check: ANSI stripping can split a multi-byte UTF-8 sequence.

Source

Thrown at crates/server/src/main.rs:102

    deployment
        .container()
        .backfill_repo_names()
        .await
        .map_err(DeploymentError::from)?;
    deployment
        .track_if_analytics_allowed("session_start", serde_json::json!({}))
        .await;
    // Preload global executor options cache for all executors with DEFAULT presets
    tokio::spawn(async move {
        executors::executors::utils::preload_global_executor_options_cache().await;
    });
    let port = std::env::var("BACKEND_PORT")
        .or_else(|_| std::env::var("PORT"))
        .ok()
        .and_then(|s| {
            // Remove any ANSI codes, then turn into String
            let cleaned =
                String::from_utf8(strip(s.as_bytes())).expect("UTF-8 after stripping ANSI");
            cleaned.trim().parse::<u16>().ok()
        })
        .unwrap_or_else(|| {
            tracing::info!("No PORT environment variable set, using port 0 for auto-assignment");
            0
        }); // Use 0 to find free port if no specific port provided

    let proxy_port = std::env::var("PREVIEW_PROXY_PORT")
        .ok()
        .and_then(|s| s.trim().parse::<u16>().ok())
        .unwrap_or(0);

    let host = std::env::var("HOST").unwrap_or_else(|_| "127.0.0.1".to_string());

    let main_listener = tokio::net::TcpListener::bind(format!("{host}:{port}")).await?;
    let actual_main_port = main_listener.local_addr()?.port();

    let proxy_listener = tokio::net::TcpListener::bind(format!("{host}:{proxy_port}")).await?;

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Inspect the variable's raw bytes (echo -n "$BACKEND_PORT" | xxd) and rewrite it as plain ASCII digits.
  2. Re-export cleanly: export BACKEND_PORT=3001 with no control characters.
  3. Unset the variable so the code falls back to auto-assigning port 0.
  4. Fix the launcher script or process manager embedding ANSI codes in the value.
Defensive patterns

Strategy: validation

Validate before calling

fn parse_port(raw: &str) -> Option<u16> {
    let cleaned = String::from_utf8(strip(raw.as_bytes())).ok()?;
    cleaned.trim().parse::<u16>().ok()
}
// pre-check before launch:
if let Ok(v) = std::env::var("BACKEND_PORT").or_else(|_| std::env::var("PORT")) {
    if parse_port(&v).is_none() && !v.trim().is_empty() {
        eprintln!("PORT/BACKEND_PORT is not a valid port: {v:?}");
    }
}

Type guard

fn is_ascii_port(s: &str) -> bool {
    !s.is_empty()
        && s.bytes().all(|b| b.is_ascii_digit())
        && s.parse::<u16>().is_ok()
}

Try / catch

let port = std::env::var("BACKEND_PORT")
    .or_else(|_| std::env::var("PORT"))
    .ok()
    .and_then(|s| String::from_utf8(strip(s.as_bytes())).ok())
    .and_then(|c| c.trim().parse::<u16>().ok())
    .unwrap_or(0); // fall back to auto-assign instead of panicking

Prevention

When it happens

Trigger: BACKEND_PORT or PORT contains binary/non-UTF-8 bytes such that strip() leaves an invalid UTF-8 sequence — e.g. the env var was set from raw bytes, a script emitted garbage, or a truncated ANSI sequence split a multi-byte character.

Common situations: Port injected by a process manager or shell wrapper that embeds ANSI codes; truncated or corrupted environment variable at process spawn; copy-pasted control characters into a config file or shell export; Windows console encoding artifacts.

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/06d60d07993872d0. Report an issue: GitHub.