tonhowtf/omniget · error · anyhow::Error

Failed to fetch AES key after

Error message

Failed to fetch AES key after {} attempts

What it means

Terminal error of fetch_key_with_retry: all attempts to download the AES key failed and no more specific error is available, so a message naming the attempt count is returned. The download cannot proceed since segments are encrypted.

Solutions

  1. Log per-attempt errors to reveal the true cause (status code vs network error).
  2. Increase max_retries/backoff, and honor Retry-After on 429.
  3. Refresh the playlist to get a new key URI before retrying.
  4. Verify headers/cookies required by the key server match those used for segments.

Example fix

// before
let key = fetch_key_with_retry(client, &key_uri, referer, 3).await?;
// after
let key = fetch_key_with_retry(client, &key_uri, referer, 5)
    .await
    .with_context(|| format!("AES key fetch failed for {key_uri}"))?;
Defensive patterns

Strategy: retry

Validate before calling

let max_retries = max_retries.max(3); // never zero attempts for encrypted streams
// and pre-check reachability of the key URI with a HEAD request

Try / catch

let key = fetch_key_with_retry(client, &key_uri, referer, 5).await
    .with_context(|| format!("AES key unavailable after retries: {key_uri}"))?;

Prevention

When it happens

Trigger: Every retry to GET the key URI failed (transport errors or non-2xx statuses) across max_retries; effectively when last_err is None (e.g., max_retries=0).

Common situations: Key server outage; client hard-blocked by the key CDN; extremely short-lived key URLs; too few retries against a rate-limited endpoint.

Related errors


AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/c8e574cc78b49780. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/omniget-core/src/core/hls_downloader.rs:493

                    if !resp.status().is_success() {
                        last_err = Some(anyhow::anyhow!("HTTP {} fetching AES key", resp.status()));
                    } else {
                        match resp.bytes().await {
                            Ok(bytes) => return Ok(bytes.to_vec()),
                            Err(e) => last_err = Some(anyhow::anyhow!(e)),
                        }
                    }
                }
                Err(e) => last_err = Some(anyhow::anyhow!(e)),
            }
            if attempt < max_retries - 1 {
                let base = 500 * (attempt as u64 + 1);
                let jitter = rand::random::<u64>() % (base / 2 + 1);
                tokio::time::sleep(Duration::from_millis(base + jitter)).await;
            }
        }
        Err(last_err.unwrap_or_else(|| {
            anyhow::anyhow!("Failed to fetch AES key after {} attempts", max_retries)
        }))
    }
}

struct EncryptionInfo {
    key_bytes: Vec<u8>,
    iv: Option<[u8; 16]>,
}

/// Attach `Referer` (and a matching `Origin`) headers to a request.
/// An empty referer means "send no Referer/Origin at all" — some CDNs
/// reject requests with a wrong Referer but accept ones without any.
fn apply_referer_headers(req: reqwest::RequestBuilder, referer: &str) -> reqwest::RequestBuilder {
    if referer.is_empty() {
        return req;
    }
    let req = req.header("Referer", referer);
    match url_origin(referer) {

View on GitHub (pinned to 8600b91f42)