jdx/mise · error

Got HTML instead of text from {url}

Error message

Got HTML instead of text from {url}

What it means

mise fetched a URL expecting plain text but the response was detected as HTML. For http:// URLs mise automatically retries over https://; if the URL is already https (or the retry also returned HTML), it fails with this error. Typically the endpoint returned an error page, login page, or proxy block page instead of the expected text payload.

Source

Thrown at src/http.rs:1560

            .client
            .send_with_https_fallback_with_retries(
                Method::GET,
                url.clone(),
                &headers,
                "GET",
                self.retries,
                true,
            )
            .await?;
        let text = resp.text().await?;
        if text.starts_with("<!DOCTYPE html>") {
            if url.scheme() == "http" {
                // try with https since http may be blocked
                url.set_scheme("https").unwrap();
                self.url = Ok(url);
                return Box::pin(self.send()).await;
            }
            bail!("Got HTML instead of text from {}", url);
        }
        Ok(text)
    }
}

fn is_github_forbidden(url: &Url, resp: &Response) -> bool {
    resp.status() == StatusCode::FORBIDDEN && url.host_str() == Some("api.github.com")
}

fn is_github_unauthorized(url: &Url, resp: &Response) -> bool {
    resp.status() == StatusCode::UNAUTHORIZED && crate::github::is_github_api_url(url)
}

/// Maximum body bytes buffered when building a GitHub error report, so an
/// oversized or slow-trickling error response can't exhaust memory. The overall
/// request timeout bounds the time; this bounds the memory.
const MAX_ERROR_BODY_BYTES: usize = 64 * 1024;

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Use the raw-content URL (e.g. raw.githubusercontent.com instead of github.com) so the server returns text, not HTML.
  2. Bypass the intercepting proxy/captive portal: fix proxy env vars (HTTPS_PROXY/NO_PROXY) or authenticate to the network.
  3. Check that the custom mirror/backend URL is correct and serves plain text.

Example fix

// before
MISE_NODE_MIRROR=https://github.com/nodejs/node/releases/download
// after: point at an endpoint serving plain text/real artifacts
MISE_NODE_MIRROR=https://nodejs.org/dist
Defensive patterns

Strategy: validation

Validate before calling

let body = client.get(url).send().await?.text().await?;
let looks_html = body.trim_start().starts_with('<')
    && (body.contains("<html") || body.contains("<!DOCTYPE html"));
if looks_html { return Err("endpoint returned HTML; check URL/proxy".into()); }

Type guard

fn is_html(body: &str) -> bool {
    let t = body.trim_start();
    (t.starts_with('<') && (t.contains("<html") || t.contains("<!DOCTYPE")))
}

Try / catch

match resource.text().await {
    Err(e) if e.to_string().contains("Got HTML instead of text") => {
        eprintln!("check the URL serves raw text and no proxy intercepts it");
        std::process::exit(1);
    }
    other => other?,
}

Prevention

When it happens

Trigger: Requesting a plain-text resource (e.g. a version list or config file) with HttpResource::text / get_text where the response body is HTML; http:// URL retried over https:// still yields HTML; a captive portal or proxy serves an HTML interstitial.

Common situations: Corporate proxy or captive portal intercepting requests; pointing mise at a URL that serves an HTML dashboard instead of raw text (e.g. a GitHub HTML page instead of a raw file); a mirror returning 200 with an HTML error page.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/dddbb7c3298a9f5f. Report an issue: GitHub.