denoland/deno · error · AnyError
cabinet file missing
Error message
cabinet file {name} missing What it means
The Windows installer embeds files in a cabinet (.cab) stream: files are added to the cabinet writer up front, and when the writer yields each entry by name the code looks the bytes up by MSI File-table key (by_key map). This error means the writer produced an entry whose name has no corresponding MsiFile — the cabinet's name stream and the File-table keys disagree.
Solutions
- Dump the file list feeding the .msi build and check for duplicate or empty MSI File keys; dedupe them at the source.
- Verify each key round-trips through the cabinet writer's name encoding (ASCII, no path separators) before adding.
- If keys are correct, log both the emitted cab name and the known keys at build time to spot the normalization difference, then normalize keys to cab names.
- Report with the exact name from the error — it pinpoints which entry diverged.
Example fix
// before: duplicates collapse in the map, writer still emits both let by_key: HashMap<&str, &MsiFile> = files.iter().map(|f| (f.key.as_str(), f)).collect(); // files contains key="app.dll" twice -> one map entry, two cab entries -> "cabinet file app.dll missing" // after: dedupe/validate keys up front let mut seen = std::collections::HashSet::new(); files.retain(|f| !f.key.is_empty() && seen.insert(f.key.clone()));
Defensive patterns
Strategy: validation
Validate before calling
// Validate the file list before building the cabinet: keys must be unique and non-empty
fn valid_file_list(files: &[MsiFile]) -> Result<(), String> {
let mut seen = std::collections::HashSet::new();
for f in files {
if f.key.is_empty() { return Err(format!("empty MSI key for {}", f.abs_path.display())); }
if !seen.insert(f.key.clone()) { return Err(format!("duplicate MSI key: {}", f.key)); }
}
Ok(())
} Try / catch
Catch the error, log the missing name from the message together with the full key set, and fail the build — the name in the error identifies exactly which cab entry lacks a matching MsiFile, making the divergent producer easy to fix.
Prevention
- Deduplicate and validate MSI File keys where the file list is assembled, not inside the cabinet writer loop.
- Keep keys ASCII, path-separator-free, and identical to the cab names you register.
- When adding a new file to the installer, always add both the cab entry and its MsiFile in the same change.
When it happens
Trigger: A file was added to the cabinet under a name/cab-name that does not equal its MSI File key: duplicate keys in the file list (two entries collapse in the HashMap while the writer emits both), an empty or malformed key, or encoding/normalization differences between the cab name and the key string.
Common situations: The packaged file list accidentally contains two entries with the same key (e.g. same app name added for two architectures); a key contains characters the cabinet encoder transforms (case folding, path separators, non-ASCII); a future code path adds a cab entry without registering its MsiFile.
Related errors
- malformed .msi: no _Columns stream
- malformed .msi: no _StringData stream
- malformed .msi: no _StringPool stream
- Cannot build a .msi from an empty app directory
- deno.json `version` " " cannot be used as an MSI…
AI-assisted analysis of denoland/deno@f7822238ca (2026-08-20).
Data as JSON: /api/errors/fb7c107e84cac212.
Report an issue: GitHub.
Appendix: source
Thrown at cli/tools/desktop.rs:5452
folder.add_file(f.key.clone());
}
}
let cursor = std::io::Cursor::new(Vec::<u8>::new());
let mut writer = builder
.build(cursor)
.context("failed to start cabinet for .msi")?;
// Files come back in the order they were added; read each one's bytes by its
// cab name (the MSI File key) to stay robust to ordering.
let by_key: std::collections::HashMap<&str, &MsiFile> =
files.iter().map(|f| (f.key.as_str(), f)).collect();
while let Some(mut file_writer) = writer
.next_file()
.context("failed to advance cabinet writer")?
{
let name = file_writer.file_name().to_string();
let f = by_key.get(name.as_str()).ok_or_else(|| {
deno_core::anyhow::anyhow!("cabinet file {name} missing")
})?;
let data = std::fs::read(&f.abs_path).with_context(|| {
format!("failed to read {} for .msi cabinet", f.abs_path.display())
})?;
file_writer.write_all(&data)?;
}
let cursor = writer.finish().context("failed to finish cabinet")?;
Ok(cursor.into_inner())
}
/// Recursively copy a directory tree, ensuring writable permissions on the
/// destination. This is needed because source files from the Nix store are
/// read-only.
/// Strip unnecessary files from a CEF-based app bundle to reduce size.
///
/// Removes:
/// - Non-English locale packs (~47MB)
/// - SwiftShader software Vulkan renderer (~16MB, not needed on macOS with Metal)View on GitHub (pinned to f7822238ca)