From 946275a6117f13cd8a310898e605121efc050b1c Mon Sep 17 00:00:00 2001 From: rUv Date: Thu, 18 Jun 2026 23:06:54 -0400 Subject: [PATCH] fix(ruvllm-cli): follow HF 307 redirect on aux-file download (#590) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * fix(ruvllm-cli): follow HF 307 redirect on aux-file download (curl -L fallback) `ruvllm download ` 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//resolve//), 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 '*.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 * style(ruvllm-cli): rustfmt Co-Authored-By: claude-flow --------- Co-authored-by: ruvnet --- crates/ruvllm-cli/src/commands/download.rs | 55 ++++++++++++++++--- ...DR-259-ruvllm-darwin-mode-local-mutator.md | 25 ++++++++- 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/crates/ruvllm-cli/src/commands/download.rs b/crates/ruvllm-cli/src/commands/download.rs index 7a280b60d..30d1df383 100644 --- a/crates/ruvllm-cli/src/commands/download.rs +++ b/crates/ruvllm-cli/src/commands/download.rs @@ -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 { // 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 { + 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 { let mut files = vec![ diff --git a/docs/adr/ADR-259-ruvllm-darwin-mode-local-mutator.md b/docs/adr/ADR-259-ruvllm-darwin-mode-local-mutator.md index 073620572..edc8a764a 100644 --- a/docs/adr/ADR-259-ruvllm-darwin-mode-local-mutator.md +++ b/docs/adr/ADR-259-ruvllm-darwin-mode-local-mutator.md @@ -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)