denoland/deno · error · AnyError
malformed .msi: no _StringData stream
Error message
malformed .msi: no _StringData stream
What it means
Companion check to the _StringPool error: after locating the string-pool header stream, the repacker needs the _StringData stream (the concatenated UTF-8 string bytes the pool indexes into). Its absence with the same find_raw/de-mangle lookup means the MSI's string table is half-present — effectively a malformed or non-MSI input, since every valid MSI writes both streams together.
Solutions
- Verify integrity of the template: compare its size/SHA-256 against the known-good source, re-download it.
- Open the file with lessmsi/msidb to confirm both string-table streams are present before repacking.
- If building the template in-house, ensure the MSI authoring tool completed successfully (check its exit status and logs).
Example fix
# before: template truncated by an interrupted download ls -l template.msi # 41 KB (expected ~3 MB) # error: malformed .msi: no _StringData stream # after: refetch and verify sha256sum template.msi # match against known-good digest, then rebuild
Defensive patterns
Strategy: validation
Validate before calling
7z l template.msi | grep -q '_StringData' || { echo 'MSI string data stream missing — template truncated/corrupt'; exit 1; }
sha256sum template.msi # compare against the digest recorded when the template was first obtained Try / catch
Treat like 1795: catch, verify the template digest against its known-good value, and abort the build on mismatch; do not attempt partial recovery of a half-present string table.
Prevention
- Pin and verify template digests in CI so truncated downloads are caught before repacking.
- Ensure template downloads are atomic (download to temp, checksum, then move into place).
When it happens
Trigger: find_raw("_StringData") misses after _StringPool was found: a truncated compound file where later streams were cut off, a hand-crafted/corrupted database, or stream-name mangling that only some names survive.
Common situations: Truncated template download (pool near the start survived, data stream at the end did not); disk-full while the template was written; a partial copy from an artefact store.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- malformed .msi: no _Columns stream
- malformed .msi: no _StringPool stream
- cabinet file missing
- 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/8a028ec075927898.
Report an issue: GitHub.
Appendix: source
Thrown at cli/tools/desktop.rs:5303
.cloned()
};
// The string-pool header's top bit selects 3-byte string references; tiny
// databases use 2-byte refs, but honor the flag to stay correct at any size.
let pool_raw = find_raw("_StringPool").ok_or_else(|| {
deno_core::anyhow::anyhow!("malformed .msi: no _StringPool stream")
})?;
let pool = read_stream(&mut comp, &pool_raw)?;
let str_w: usize = if pool.len() >= 4 && (pool[3] & 0x80) != 0 {
3
} else {
2
};
// Map every string-pool id to its text so data-table streams (named by table
// text) can be matched to their column schema (keyed by table string id).
let data_raw = find_raw("_StringData").ok_or_else(|| {
deno_core::anyhow::anyhow!("malformed .msi: no _StringData stream")
})?;
let str_data = read_stream(&mut comp, &data_raw)?;
let mut strings = vec![String::new()]; // 1-based; index 0 is unused.
{
let mut off = 0usize;
let mut i = 4;
while i + 4 <= pool.len() {
let len = u16::from_le_bytes([pool[i], pool[i + 1]]) as usize;
let end = (off + len).min(str_data.len());
strings.push(String::from_utf8_lossy(&str_data[off..end]).into_owned());
off += len;
i += 4;
}
}
let id_of = |text: &str| -> Option<u64> {
strings.iter().position(|s| s == text).map(|i| i as u64)
};
View on GitHub (pinned to f7822238ca)