tonhowtf/omniget · error
HTTP
Error message
HTTP {} What it means
fetch_to downloads a single media file from x.com CDN and throws `HTTP {}` when the GET response status is not a success (2xx). The library checks `resp.status().is_success()` after sending the request and aborts early, before writing the .part file, so no partial download is left on disk. The status code is embedded in the message (e.g. `HTTP 404 Not Found`).
Solutions
- Re-fetch the tweet metadata (fxtwitter or GraphQL) to get a fresh media URL, then retry the download immediately after fetching
- Retry with backoff on 429/5xx statuses; treat 404/410 as permanent and skip that media item
- Ensure the Referer/UA headers are preserved (some CDN paths reject bare requests); log resp.status() to distinguish permanent vs transient failures
Example fix
// before
if !resp.status().is_success() {
return Err(anyhow!("HTTP {}", resp.status()));
}
// after
if resp.status() == reqwest::StatusCode::TOO_MANY_REQUESTS || resp.status().is_server_error() {
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
// retry the request once before giving up
}
if !resp.status().is_success() {
anyhow::bail!("HTTP {} for {}", resp.status(), url);
} Defensive patterns
Strategy: retry
Validate before calling
// optionally pre-check reachability (rarely possible for signed URLs)
let head = client.head(media_url).send().await?;
if !head.status().is_success() {
eprintln!("midia indisponivel: {}", head.status()); // skip before calling download path
} Type guard
fn is_retryable(status: reqwest::StatusCode) -> bool {
status == reqwest::StatusCode::TOO_MANY_REQUESTS || status.is_server_error()
} Try / catch
match fetch_media(url, path).await {
Err(e) if e.to_string().starts_with("HTTP 429") || e.to_string().starts_with("HTTP 5") => {
tokio::time::sleep(Duration::from_secs(5)).await; // retry once
}
Err(e) if e.to_string().starts_with("HTTP 40") => skip_item(e), // permanent: skip
Err(e) => return Err(e),
Ok(()) => {}
} Prevention
- Download media immediately after fetching tweet metadata, before signed CDN URLs expire
- Treat 404/410 as permanent and skip; retry only 429/5xx with backoff
- Always send Referer/UA headers matching the library's request
- Keep the atomic .part+rename pattern so failed downloads never leave corrupt files
When it happens
Trigger: Calling download_posts which calls fetch_to; the reqwest GET (with Referer header https://x.com/) to the media URL returns 403/404/410/5xx. Typical causes: the media URL expired (signed CDN URLs from fxtwitter/GraphQL expire), the tweet/media was deleted, or the CDN rejected the request for missing auth/rate limiting.
Common situations: Downloading media from old or deleted tweets whose pbs.twimg.com URLs returned 410 Gone; expired signed URLs after the tweet JSON was fetched minutes earlier; Twitter CDN throttling with 429; corporate proxy returning 403.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- download falhou: HTTP
- download de falhou: HTTP
- Twitch GQL não respondeu depois de 5 tentativas
- Failed to download attachment
- nao foi possivel buscar
AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12).
Data as JSON: /api/errors/1180e4ddc3831b4c.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/omniget-core/src/core/tools/x/media.rs:137
tokio::time::sleep(std::time::Duration::from_millis(250)).await;
}
}
super::report(progress, job, "done", done as u64, Some(total as u64), None);
Ok(result)
}
async fn fetch_to(
client: &reqwest::Client,
url: &str,
path: &std::path::Path,
) -> anyhow::Result<()> {
let resp = client
.get(url)
.header("Referer", "https://x.com/")
.send()
.await?;
if !resp.status().is_success() {
return Err(anyhow!("HTTP {}", resp.status()));
}
let bytes = resp.bytes().await?;
let part = path.with_extension("part");
tokio::fs::write(&part, &bytes).await?;
tokio::fs::rename(&part, path).await?;
Ok(())
}
/// Todas as midias publicas de um perfil (aba Midia), ate `limit` posts.
pub async fn download_profile(
input: &str,
dest: &str,
limit: usize,
photos: bool,
videos: bool,
progress: ProgressFn,
) -> anyhow::Result<MediaResult> {
let handle = super::handle_from(input)View on GitHub (pinned to 8600b91f42)