From f53ee26213c7ef92a0759364f5d64694ebfb534c Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Thu, 9 Apr 2026 13:27:46 -0700 Subject: [PATCH] fix: detect and explain non-GGUF model files after download When a proxy or firewall intercepts HuggingFace downloads, the cached file is an HTML page instead of a GGUF model. Previously this surfaced as an opaque "Invalid GGUF magic" error from node-llama-cpp. Now we validate the GGUF magic bytes right after download, detect HTML pages specifically, delete the bad file, and show an actionable error message with workarounds (HF_ENDPOINT mirror, manual download path). Co-Authored-By: Claude Opus 4.6 --- src/llm.ts | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/src/llm.ts b/src/llm.ts index 4d2a03b..24259ea 100644 --- a/src/llm.ts +++ b/src/llm.ts @@ -16,7 +16,7 @@ import { } from "node-llama-cpp"; import { homedir } from "os"; import { join } from "path"; -import { existsSync, mkdirSync, statSync, unlinkSync, readdirSync, readFileSync, writeFileSync } from "fs"; +import { existsSync, mkdirSync, statSync, unlinkSync, readdirSync, readFileSync, writeFileSync, openSync, readSync, closeSync } from "fs"; // ============================================================================= // Embedding Formatting Functions @@ -248,6 +248,58 @@ async function getRemoteEtag(ref: HfRef): Promise { } } +const GGUF_MAGIC = Buffer.from("GGUF"); + +/** + * Validate that a file is actually a GGUF model, not an HTML error page + * from a proxy, firewall, or failed download. + * Throws a descriptive error if the file is not valid GGUF. + */ +function validateGgufFile(filePath: string, modelUri: string): void { + if (!existsSync(filePath)) return; // let downstream handle missing files + + // Read header + sniff bytes in one go, then close immediately + const fd = openSync(filePath, "r"); + const sniff = Buffer.alloc(512); + try { + readSync(fd, sniff, 0, 512, 0); + } finally { + closeSync(fd); + } + + const header = sniff.subarray(0, 4); + if (header.equals(GGUF_MAGIC)) return; // valid GGUF + + const text = sniff.toString("utf-8").toLowerCase(); + const isHtml = text.includes(" { this.ensureModelCacheDir(); // resolveModelFile handles HF URIs and downloads to the cache dir - return await resolveModelFile(modelUri, this.modelCacheDir); + const modelPath = await resolveModelFile(modelUri, this.modelCacheDir); + validateGgufFile(modelPath, modelUri); + return modelPath; } /**