tonhowtf/omniget · error · anyhow::Error

stream error: {}

Error message

stream error: {}

What it means

Thrown in download_streaming when the response body stream itself yields an error (Ok(Some(Err(e))) from the bytes_stream). This means the HTTP connection broke mid-download — the transport or hyper layer produced an I/O error while reading a chunk — as opposed to a timeout (handled separately) or a bad status code. The .part file retains everything downloaded before the failure and can be used for resume.

Solutions

  1. Retry the download with resume enabled so the .part file is continued rather than restarted (Range request / sidecar resume).
  2. Wrap the download call in a retry loop with exponential backoff for transient network errors.
  3. Reduce exposure to idle-connection kills by lowering chunk read time or verifying the server's keep-alive/timeout settings.
  4. Inspect the wrapped io/hyper error (the {} in the message) for the precise cause (e.g. ConnectionReset, UnexpectedEof) and address it specifically.
  5. Test with a wired/stable network or different DNS/VPN to rule out local network instability.

Example fix

// before
fetcher.download(&mut progress_tx).await?; // one shot, no resume

// after
for attempt in 0..5 {
    match fetcher.download(&mut progress_tx).await {
        Ok(()) => break,
        Err(e) if attempt < 4 && is_transient_stream_error(&e) => {
            tokio::time::sleep(Duration::from_secs(2u64.pow(attempt))).await;
        }
        Err(e) => return Err(e),
    }
}
Defensive patterns

Strategy: retry

Validate before calling

// sanity-check reachability before a long transfer
let resp = client.get(&url).send().await?;
if !resp.status().is_success() {
    bail!("source unreachable: {}", resp.status());
}

Try / catch

match fetcher.download(&mut tx).await {
    Err(e) if e.to_string().starts_with("stream error") => {
        // transient mid-stream failure: resume from .part with backoff
        tokio::time::sleep(Duration::from_secs(2)).await;
        fetcher.download_resuming(&mut tx).await?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: resp.bytes_stream() returns an Err item during the loop: connection reset by peer (RST), TLS handshake renegotiation failure, premature EOF (server closed connection without completing Content-Length), HTTP/2 GOAWAY, or proxy dropped the connection mid-transfer.

Common situations: Flaky Wi-Fi/mobile network or VPN drop mid-download; server or load balancer idle-killing long transfers; CDN edge node restarting; corporate proxy terminating long-lived connections; HTTP/2 stream reset by the server.

Related errors


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

Appendix: source

Thrown at src-tauri/omniget-core/src/core/http_fetcher.rs:375

                            None => (
                                ((downloaded as f64 / (downloaded as f64 + 500_000.0)) * 100.0)
                                    .min(95.0),
                                None,
                            ),
                        };
                        let _ = progress_tx
                            .send(ProgressUpdate::rich(
                                pct,
                                Some(downloaded),
                                total.filter(|t| *t > 0),
                                speed,
                                eta,
                            ))
                            .await;
                        last_emit = std::time::Instant::now();
                    }
                }
                Ok(Some(Err(e))) => return Err(anyhow!("stream error: {}", e)),
                Ok(None) => break,
                Err(_) => {
                    return Err(anyhow!(
                        "read timed out after {:?}",
                        self.config.read_timeout
                    ))
                }
            }
        }

        file.flush().await?;
        drop(file);
        tokio::fs::rename(part_path, &self.output_path).await?;
        let _ = progress_tx.send(ProgressUpdate::percent(100.0)).await;
        Ok(HttpFetcherResult {
            bytes_written: downloaded,
        })
    }

View on GitHub (pinned to 8600b91f42)