astrid-runtime/astrid · error
durable capsule component path is not UTF-8
Error message
durable capsule {id} component path is not UTF-8 What it means
verify_package_identity reads the first component's path from the manifest and converts it to a UTF-8 string via Path::to_str, because archive entries are keyed by string paths. This bail fires when the component path contains bytes that are not valid UTF-8. The library throws it because archive lookup and downstream path handling require string keys, and non-UTF-8 paths cannot be matched safely against archive entries.
Solutions
- Rebuild the capsule so the component path in the manifest is valid UTF-8 (ASCII-safe relative path like 'component.wasm').
- Fix the manifest's component.path field to a UTF-8 string and repack the archive.
- Reject the offending package upstream and regenerate it with normalized (UTF-8) filenames.
- Validate every manifest component path with to_str()/String::from_utf8 before packaging to catch this at build time.
Example fix
// before: path from raw OS bytes let path = PathBuf::from(OsString::from_vec(raw_bytes)); // not UTF-8 // after: normalize to a UTF-8 relative path when building the manifest let path = PathBuf::from(String::from_utf8(raw_bytes).map_err(|_| "non-UTF-8 component path")?);
Defensive patterns
Strategy: validation
Validate before calling
fn assert_utf8_component_paths(manifest: &CapsuleManifest) -> anyhow::Result<()> {
for component in &manifest.components {
component.path.to_str().ok_or_else(|| anyhow!("component path {:?} is not UTF-8", component.path))?;
}
Ok(())
} Type guard
fn has_utf8_path(p: &std::path::Path) -> bool { p.to_str().is_some() } Prevention
- Build packages in UTF-8 locales and restrict component filenames to ASCII.
- Validate manifest paths at packaging time, not just install time.
- Avoid passing raw OsString filenames into manifest generation.
When it happens
Trigger: Installing a durable capsule whose CapsuleManifest declares a component with a non-UTF-8 path (e.g. built on a filesystem with raw-byte filenames or a manifest generated by a tool writing arbitrary bytes into the path field), when read_verified_durable_package_for_owner runs identity verification.
Common situations: Packages built on legacy/non-UTF-8 locales (e.g. Latin-1 filenames) then moved into archives; hand-crafted or programmatically generated manifests with escaped/mojibake path bytes; tar tools preserving odd encodings on extraction or repack.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- capsule path is not valid UTF-8
- legacy capsule entry has a non-UTF-8 name
- absent migration source has a digest
- alice
- an incomplete capsule authority update exists at
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/ed0b4b866fa2a7ba.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-capsule-install/src/storage.rs:296
}
let expected_imports = crate::wit::version_map_to_strings(&manifest.imports, |definition| {
definition.version.to_string()
});
if metadata.imports != expected_imports {
bail!("durable capsule {id} imports differ between metadata and archive");
}
let expected_exports = crate::wit::version_map_to_strings(&manifest.exports, |definition| {
definition.version.to_string()
});
if metadata.exports != expected_exports {
bail!("durable capsule {id} exports differ between metadata and archive");
}
if authority.wasm_hash_pinned && metadata.wasm_hash != authority.approved_wasm_hash {
bail!("durable capsule {id} metadata executable hash differs from authority receipt");
}
if let Some(component) = manifest.components.first() {
let Some(relative) = component.path.to_str() else {
bail!("durable capsule {id} component path is not UTF-8");
};
let Some(bytes) = archive_files.get(relative) else {
bail!("durable capsule {id} component is missing from its archive");
};
if Path::new(relative)
.extension()
.is_some_and(|extension| extension.eq_ignore_ascii_case("wasm"))
{
let archive_hash = blake3::hash(bytes).to_hex().to_string();
if authority.wasm_hash_pinned
&& authority.approved_wasm_hash.as_deref() != Some(archive_hash.as_str())
{
bail!("durable capsule {id} WASM hash differs between authority and archive");
}
if metadata.wasm_hash.as_deref() != Some(archive_hash.as_str()) {
bail!("durable capsule {id} WASM hash differs between metadata and archive");
}
} else if metadata.wasm_hash.is_some() {View on GitHub (pinned to affd8760f4)