astrid-runtime/astrid · error
durable capsule is missing Capsule.toml
Error message
durable capsule {id} is missing Capsule.toml What it means
read_verified_durable_package_for_owner verifies the durable capsule's archive against its recorded checksum and then enumerates its files, requiring a top-level 'Capsule.toml' manifest entry. If the verified archive contains no Capsule.toml, the package is structurally invalid and this error is raised before any manifest parsing.
Solutions
- List the archive entries and confirm Capsule.toml sits at the archive root; rebuild with the package root as tar's working directory.
- Re-run the directory publish (publish_directory_package / capsule install) so the archive is regenerated correctly from the capsule source.
- Check packaging scripts/exclude patterns for rules that drop Capsule.toml.
- If the capsule was migrated from an older format, re-migrate with the current tool version.
Example fix
// before (from parent dir — manifest nested) tar -cf capsule.tar ./my-capsule // after (from package root — Capsule.toml at top level) cd my-capsule && tar -cf ../capsule.tar .
Defensive patterns
Strategy: validation
Validate before calling
fn archive_has_root_manifest(tar_path: &std::path::Path) -> anyhow::Result<bool> {
let f = std::fs::File::open(tar_path)?;
let mut tar = tar::Archive::new(std::io::BufReader::new(f));
for entry in tar.entries()? {
let name = entry?.path()?.to_path_buf();
if name.file_name().map(|n| n == "Capsule.toml").unwrap_or(false)
&& name.parent().map(|p| p.as_os_str().is_empty() || p == Path::new(".")).unwrap_or(true) {
return Ok(true);
}
}
Ok(false)
} Prevention
- Always create archives from inside the package root so Capsule.toml is at the top level.
- Check packaging exclude patterns don't drop Capsule.toml.
- Verify published archives with read_verified_durable_package before distributing.
When it happens
Trigger: Any caller (durable_contracts_pin, refresh_canonical_contracts_from_registry, durable_capsule_metadata, read_verified_durable_package, archive-entry tests, leftover matching) reads a durable capsule whose archive was built without the manifest at the archive root — e.g. the manifest ended up under a subdirectory like ./Capsule.toml or was omitted by the packaging tool.
Common situations: A hand-rolled tar built from the wrong directory so Capsule.toml is nested one level deep; packaging scripts that exclude dotfiles/manifests; archives migrated by an older tool version with a different layout.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- authenticated AstridFS.app is redirected or not a directory
- authenticated macOS lifecycle tool is redirected or not…
- branding.accent-color
- branding.icon is bytes — distros must not embed assets…
- branding.primary-color
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/b7c07a6a6bb2aa91.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-capsule-install/src/storage.rs:169
/// prefer [`read_verified_durable_package`], which takes an immutable UID.
pub fn read_verified_durable_package_for_owner(
store: &RuntimePrincipalStore,
owner: &StateOwner,
id: &str,
) -> anyhow::Result<Option<VerifiedDurableCapsulePackage>> {
let registry = store.capsules();
let Some(snapshot) = registry.get_snapshot(owner, id)? else {
return Ok(None);
};
let package = snapshot.package();
let verification = artifact::verify_archive_bytes(&package.archive)
.with_context(|| format!("verify durable capsule archive {id}"))?;
let inventory = read_archive_files(&package.archive)?;
let ArchiveInventory { files, directories } = inventory;
let manifest_bytes = files
.get("Capsule.toml")
.cloned()
.ok_or_else(|| anyhow::anyhow!("durable capsule {id} is missing Capsule.toml"))?;
let manifest_text = String::from_utf8(manifest_bytes.clone())
.with_context(|| format!("durable capsule {id} manifest is not UTF-8"))?;
let manifest: CapsuleManifest = toml::from_str(&manifest_text)
.with_context(|| format!("decode durable capsule {id} manifest"))?;
let metadata: CapsuleMeta = serde_json::from_slice(&package.metadata)
.with_context(|| format!("decode durable capsule {id} metadata"))?;
let authority: InstalledAuthority = serde_json::from_slice(&package.authority)
.with_context(|| format!("decode durable capsule {id} authority"))?;
let metadata_bytes = package.metadata.clone();
verify_package_identity(
id,
&manifest,
&metadata,
&authority,
&manifest_bytes,
&verification,
&files,
)?;View on GitHub (pinned to affd8760f4)