fix(ruvllm-cli): follow HF 307 redirect on aux-file download (#590)

* docs(adr-259): mark RuvllmMutator implemented (code+tests+CLI in @metaharness/darwin); live-serve e2e blocked by ruvllm download redirect bug

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(ruvllm-cli): follow HF 307 redirect on aux-file download (curl -L fallback)

`ruvllm download <model>` failed on aux files like tokenizer_config.json:
'Failed to download tokenizer_config.json'. The hf-hub API client doesn't follow
HuggingFace's 307 redirect to the LFS/CDN host for these files (a plain `curl -L`
on the same resolve URL returns 200). Add a redirect-following `curl -L --fail`
fallback in download_with_progress(): try hf-hub first, fall back to curl from the
HF resolve URL (https://huggingface.co/<id>/resolve/<rev>/<file>), honoring HF_TOKEN.
curl is already the download mechanism in hub/download.rs, so this is dependency-free
and consistent. Verified: tokenizer_config.json + config.json now download (2.9KB/2.5KB).

Note: a SEPARATE pre-existing bug remains — GGUF weights are requested as an unexpanded
glob '*<suffix>.gguf' (404), and the GGUF alias points at the safetensors repo; that
needs HF file-listing + registry resolution and is out of scope for this redirect fix.

Co-Authored-By: claude-flow <ruv@ruv.net>

* style(ruvllm-cli): rustfmt

Co-Authored-By: claude-flow <ruv@ruv.net>

---------

Co-authored-by: ruvnet <ruvnet@gmail.com>
This commit is contained in:
rUv 2026-06-18 23:06:54 -04:00 committed by GitHub
parent 5aa5353c87
commit 946275a611
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 72 additions and 8 deletions

View file

@ -90,7 +90,7 @@ pub async fn run(
println!(" {} {}", style("Downloading:").yellow(), file_name);
// Download with progress
let downloaded_path = download_with_progress(&repo, file_name).await?;
let downloaded_path = download_with_progress(&repo, file_name, &model_id, revision).await?;
// Copy to cache directory
tokio::fs::copy(&downloaded_path, &target_path)
@ -123,10 +123,18 @@ pub async fn run(
Ok(())
}
/// Download a file with progress indication
/// Download a file with progress indication.
///
/// Tries the `hf-hub` API first; on failure (e.g. the crate's HTTP client not
/// following HuggingFace's 307 redirect to the LFS/CDN host — observed on aux files
/// like `tokenizer_config.json` in 2.1.0), falls back to a redirect-following
/// `curl -L --fail` from the HF resolve URL. curl is already the download mechanism
/// in `hub/download.rs`, so this keeps the fix dependency-free and consistent.
async fn download_with_progress(
repo: &hf_hub::api::tokio::ApiRepo,
file_name: &str,
model_id: &str,
revision: Option<&str>,
) -> Result<PathBuf> {
// Create progress bar
let pb = ProgressBar::new(100);
@ -137,17 +145,50 @@ async fn download_with_progress(
.progress_chars("#>-"),
);
// Download file
let path = repo
.get(file_name)
.await
.context(format!("Failed to download {}", file_name))?;
// Download file (hf-hub API first, curl-redirect fallback on failure)
let path = match repo.get(file_name).await {
Ok(p) => p,
Err(e) => download_via_curl(model_id, revision, file_name)
.with_context(|| format!("Failed to download {} (hf-hub: {e})", file_name))?,
};
pb.finish_and_clear();
Ok(path)
}
/// Fallback download via `curl -L --fail` (follows HF's 307 redirect to the CDN).
/// Writes to a temp file and returns its path; the caller copies it into the cache.
fn download_via_curl(model_id: &str, revision: Option<&str>, file_name: &str) -> Result<PathBuf> {
let rev = revision.unwrap_or("main");
let url = format!("https://huggingface.co/{model_id}/resolve/{rev}/{file_name}");
let out = std::env::temp_dir().join(format!(
"ruvllm-dl-{}-{}",
std::process::id(),
file_name.replace('/', "_")
));
let mut args = vec![
"-L".to_string(), // follow redirects (the 307 hf-hub misses)
"--fail".to_string(), // non-zero exit on HTTP error
"-sS".to_string(), // quiet but show errors
"-o".to_string(),
out.to_string_lossy().to_string(),
];
if let Ok(token) = std::env::var("HF_TOKEN") {
args.push("-H".to_string());
args.push(format!("Authorization: Bearer {token}"));
}
args.push(url.clone());
let status = std::process::Command::new("curl")
.args(&args)
.status()
.with_context(|| format!("curl not available to fetch {url}"))?;
if !status.success() {
anyhow::bail!("curl fallback failed ({status}) for {url}");
}
Ok(out)
}
/// Get list of files to download for a model and quantization
fn get_files_to_download(model_id: &str, quant: QuantPreset) -> Vec<String> {
let mut files = vec![

View file

@ -1,6 +1,29 @@
# ADR-259: ruvllm as Local Mutator Backend for Darwin Mode
**Status:** Proposed
**Status:** Implemented (code + unit tests + CLI; live-serve e2e blocked by a ruvllm download bug — see Implementation status)
## Implementation status (2026-06-18)
Implemented in `agent-harness-generator` (`@metaharness/darwin`):
- `src/ruvllm-mutator.ts``RuvllmMutator implements CodeGenerator`, OpenAI-compatible
`POST /v1/chat/completions`, safe no-op fallback if the server is unreachable. Exported from `index.ts`.
- `src/cli.ts``--mutator deterministic|ruvllm` (+ `--ruvllm-url`/`--ruvllm-model`), wired to `config.generator`.
- `__tests__/ruvllm-mutator.test.ts` — 4 tests vs a real `node:http` mock (success, fence-strip,
unreachable→no-op, malformed→no-op). Full darwin suite **354/354** green.
**Honest gap:** the live-serve e2e (evolve against a real local model) is **blocked by a ruvllm
2.1.0 download bug** — `ruvllm download phi` fails on `tokenizer_config.json` (the file is served
via an HTTP 307 redirect that ruvllm does not follow; `curl -L` fetches it fine). So the HTTP
*contract* is verified by unit tests, but an end-to-end run against a served model is pending a
ruvllm fix (or manual model placement). Recommend fixing the redirect-follow in `ruvllm download`.
**Value note (ADR-087):** the mutator is not the quality lever — deterministic and frontier-LLM
mutators both hit the 0.985 scorer ceiling. RuvllmMutator's benefit is *operational* (fully local,
air-gapped, zero API cost), not higher scores.
---
**Original status:** Proposed
**Date:** 2026-06-18
**Components:**
- `crates/ruvllm` — local LLM inference runtime (RDT/OpenMythos/GGUF)