cjpais/Handy · error · anyhow::Error

server sent more than the expected

Error message

server sent more than the expected {} bytes

What it means

Raised when a server sends more bytes than the known total (catalog-pinned size or advertised Content-Length) — the cap check fires on the first chunk that would exceed it. This is a defense against misbehaving or hostile servers trying to fill the disk through an unbounded stream. Everything already written is considered tainted because the server provably lied, so the file handle is dropped and the partial deleted.

Solutions

  1. Retry — the tainted partial is already removed and the next attempt restarts cleanly.
  2. If one host consistently triggers it, switch to the other source (mirror <-> primary); this guard failing means that endpoint's responses cannot be trusted.
  3. Verify the URL serves the exact expected byte count with curl -I.
  4. Report the endpoint; over-long responses on a pinned-size object indicate a server-side fault.
Defensive patterns

Strategy: retry

Try / catch

match manager.download_model(model_id, None).await {
    Ok(_) => Ok(()),
    Err(e) if e.to_string().contains("more than the expected") => {
        // over-long body: tainted partial deleted; clean retry, then switch source if repeated
        manager.download_model(model_id, None).await
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Server ignores Range and streams a full/different body while a size is pinned; server keeps the connection open past Content-Length (infinite/broken stream); a malicious mirror padding the response.

Common situations: Misconfigured CDNs appending trailers or serving error pages mid-stream; compromised or buggy mirrors; proxies injecting content.

Related errors


AI-assisted analysis of cjpais/Handy@c89b7bf389 (2026-08-17). Data as JSON: /api/errors/1e4eb633b0ac40d7. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/src/managers/model/download.rs:347

                        DOWNLOAD_STALL_TIMEOUT.as_secs()
                    )),
                    Ok(None) => break,
                    Ok(Some(chunk)) => chunk?,
                },
                _ = cancel_token.cancelled() => {
                    // Keep the partial for resume; caller handles state cleanup.
                    return Ok(HttpDownloadOutcome::Cancelled);
                }
            };
            // An untrusted server must not be able to fill the disk: cut the
            // transfer at the first byte past the known total instead of
            // trusting it to eventually close the stream. Everything written
            // so far is tainted by a provably-misbehaving server — clear it.
            if let Some(cap) = known_total {
                if downloaded + chunk.len() as u64 > cap {
                    drop(file);
                    let _ = fs::remove_file(partial_path);
                    return Err(anyhow::anyhow!(
                        "server sent more than the expected {} bytes",
                        cap
                    ));
                }
            }
            file.write_all(&chunk)?;
            downloaded += chunk.len() as u64;
            if last_emit.elapsed() >= throttle {
                emit_progress(downloaded);
                last_emit = Instant::now();
            }
        }
        file.flush()?;
        drop(file);
        emit_progress(downloaded);

        if let Some(expected) = known_total {
            let actual = partial_path.metadata()?.len();

View on GitHub (pinned to c89b7bf389)