janhq/jan · error · Error
Invalid GGUF file(s): ${error.message || 'File format valida
Error message
Invalid GGUF file(s): ${error.message || 'File format validation failed'} What it means
Thrown by the catch block wrapping the GGUF validation section of runImport(). It calls readGgufMetadata() on the main model, the mmproj, and the optional MTP draft file; any failure (corrupt header, non-GGUF file, I/O error, missing nextn layer metadata) is caught here and re-thrown as a single Invalid GGUF file(s) error whose message embeds the underlying cause. This is the only place format-level integrity of the imported weights is enforced.
Source
Thrown at extensions/llamacpp-extension/src/index.ts:3273
if (mmprojPath) {
const fullMmprojPath = await joinPath([janDataFolderPath, mmprojPath])
const mmprojMetadata = await readGgufMetadata(fullMmprojPath)
logger.info(
`Mmproj GGUF validation successful: version ${mmprojMetadata.version}, tensors: ${mmprojMetadata.tensor_count}`
)
}
// Validate MTP draft and read its head count (the main gguf usually
// lacks nextn_predict_layers when MTP ships as a separate file).
if (mtpModelPath) {
const fullMtpPath = await joinPath([janDataFolderPath, mtpModelPath])
const mtpMetadata = await readGgufMetadata(fullMtpPath)
const draftLayers = detectMtpLayersFromGgufMeta(mtpMetadata.metadata)
mtpLayers = draftLayers > 0 ? draftLayers : Math.max(mtpLayers, 1)
}
} catch (error) {
logger.error('GGUF validation failed:', error)
throw new Error(
`Invalid GGUF file(s): ${
error.message || 'File format validation failed'
}`
)
}
// Calculate file sizes
let size_bytes = (await fs.fileStat(fullModelPath)).size
if (mmprojPath) {
size_bytes += (
await fs.fileStat(await joinPath([janDataFolderPath, mmprojPath]))
).size
}
if (mtpModelPath) {
size_bytes += (
await fs.fileStat(await joinPath([janDataFolderPath, mtpModelPath]))
).size
}View on GitHub (pinned to fad3f12a14)
Solutions
- Re-download the model file - the most common cause is a truncated/corrupt download.
- Confirm the file is genuinely a GGUF (check magic bytes 'GGUF' / 0x46554747 at the start) before importing.
- Verify mmprojPath and mtpPath point at the correct companion files, not back at the main model.
- If the GGUF is from an old llama.cpp version, reconvert/re-export with a current gguf-py or pick a newer release.
- Check disk health and free space; a corrupt-on-write file fails metadata read.
Example fix
// before
await provider.import('x', { modelPath: '/d/qwen.gguf' }) // throws - HTML error page saved as gguf
// after - validate magic before importing
async function isGguf(p) {
const f = await fs.open(p, 'r'); const buf = Buffer.alloc(4); await f.read(buf, 0, 4, 0); await f.close()
return buf.toString('ascii') === 'GGUF'
}
if (!(await isGguf('/d/qwen.gguf'))) throw new Error('not a real GGUF - re-download')
await provider.import('x', { modelPath: '/d/qwen.gguf' }) Defensive patterns
Strategy: validation
Validate before calling
// Quick GGUF magic-byte check before importing a local file
async function looksLikeGguf(p: string): Promise<boolean> {
const f = await fs.open(p, 'r'); const buf = Buffer.alloc(4)
await f.read(buf, 0, 4, 0); await f.close()
return buf.toString('ascii') === 'GGUF'
}
for (const p of [opts.modelPath, opts.mmprojPath, opts.mtpPath].filter(Boolean) as string[]) {
if (!p.startsWith('https://') && !(await looksLikeGguf(p))) throw new Error(`${p} is not a GGUF - re-download`)
} Type guard
async function isGgufFile(p: string): Promise<boolean> {
const f = await fs.open(p, 'r'); const buf = Buffer.alloc(4)
await f.read(buf, 0, 4, 0); await f.close()
return buf.readUInt32LE(0) === 0x46554747
} Try / catch
try { await provider.import(modelId, opts) }
catch (e) {
if (/Invalid GGUF/.test(String(e))) { await provider.abortImport(modelId); opts.modelPath = await redownload(); await provider.import(modelId, opts) }
else throw e
} Prevention
- Always run size + sha256 verification on downloads so truncation is caught before import.
- Check GGUF magic bytes on the picked/downloaded file before importing.
- Keep mmproj/mtp paths distinct from the main model path to avoid pointing at the wrong file.
When it happens
Trigger: The downloaded file is not actually a .gguf (HTML error page from a CDN, a .bin LLaMA pickle, a truncated download). The file is a valid GGUF but the header/metadata read failed (incomplete write, disk corruption). mmproj or mtp path points at the main model file by mistake. The mtp draft file lacks the expected metadata shape. readGgufMetadata throws on an unrecognized GGUF version.
Common situations: Download was truncated by a network drop or pause/resume but the size check was skipped. User pointed import at a stale-format GGUF from an older llama.cpp version. HuggingFace URL returned an HTML 'file not found' page that got saved as the .gguf. Cross-arch binary confusion (pointing at a compiled binary instead of weights).
Related errors
- Invalid modelId: ${modelId}. Only alphanumeric and / _ - . c
- Model ${modelId} already exists
- File not found: ${path}
- Model with ID ${model.id} already exists
- No active session found for model: ${modelId}
AI-assisted analysis of janhq/jan@fad3f12a14 (2026-08-12).
Data as JSON: /api/errors/81d31802a03f1006.
Report an issue: GitHub.