{"record":{"id":"02a9c4586c9606bc","repo":"astrid-runtime/astrid","slug":"capsule-disappeared-during-durable-contracts-sc","errorCode":null,"errorMessage":"capsule {} disappeared during durable contracts scan","messagePattern":"capsule (.+?) disappeared during durable contracts scan","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-capsule-install/src/contracts.rs","lineNumber":91,"sourceCode":"                == Some(CONTRACTS_WIT_BASENAME)\n        })\n        .min_by(|a, b| a.0.cmp(b.0))\n        .map(|(_, hash)| hash)\n}\n\n/// Return the plurality of the daemon fleet's contracts pins from its authoritative owner-root registry.\n/// The package metadata and archive are read from one durable snapshot per\n/// capsule; no native `.local/capsules` directory or cache is consulted.\npub fn durable_contracts_pin(\n    store: &RuntimePrincipalStore,\n    owner: &StateOwner,\n) -> anyhow::Result<Option<String>> {\n    let registry = store.capsules();\n    let mut counts = std::collections::BTreeMap::<String, usize>::new();\n    for summary in registry.list(owner)? {\n        let Some(package) = read_verified_durable_package_for_owner(store, owner, summary.id())?\n        else {\n            bail!(\n                \"capsule {} disappeared during durable contracts scan\",\n                summary.id()\n            );\n        };\n        let Some(pin) = contracts_pin(&package.metadata().wit_files) else {\n            continue;\n        };\n        if !is_blake3_pin(pin) {\n            bail!(\n                \"durable capsule {} has malformed contracts pin\",\n                summary.id()\n            );\n        }\n        let Some(relative) = package\n            .metadata()\n            .wit_files\n            .keys()\n            .filter(|relative| {","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/contracts.rs#L73-L109","documentation":"durable_contracts_pin iterates the authoritative capsule registry for an owner and reads each capsule's verified durable package. If registry.list returns a summary whose package cannot be read back (read_verified_durable_package_for_owner returns None), the registry is inconsistent — a listed capsule vanished mid-scan — so the library aborts with this error instead of computing a skew verdict from incomplete data.","triggerScenarios":"Calling refresh_canonical_contracts_from_registry → durable_contracts_pin when a capsule summary exists in the registry but its durable package is unreadable/unverifiable at contracts.rs:89-95 — e.g. the store was mutated concurrently, the package blob was deleted, or verification failed and returned None.","commonSituations":"A concurrent capsule uninstall/prune racing the contracts refresh; corrupted or manually pruned capsule store directories; disk-full/interrupted write leaving a registry entry without its package; differing store versions across replicas.","solutions":["Re-run the contracts refresh once no other astrid operation is mutating the store — transient races resolve on retry","Run the store's repair/prune command (or reinstall the missing capsule) to reconcile the registry with the durable packages","Check disk space and store-directory permissions; an interrupted write can leave a summary without a package","Back up and rebuild the principal store if the inconsistency persists across runs"],"exampleFix":"// before\n$ astrid contracts refresh   # while another terminal runs: astrid uninstall my-capsule\nerror: capsule my-capsule disappeared during durable contracts scan\n// after\n$ astrid uninstall my-capsule   # finish first\n$ astrid contracts refresh      # single writer -> succeeds","handlingStrategy":"retry","validationCode":"// ensure no concurrent store writers before scanning\nlet lock = acquire_store_lock(owner)?; // advisory exclusive lock across astrid processes\nlet ids: Vec<_> = store.capsules().list(owner)?.iter().map(|s| s.id().to_string()).collect();\nfor id in &ids {\n    if read_verified_durable_package_for_owner(store, owner, id)?.is_none() {\n        anyhow::bail!(\"package missing for {} before scan\", id);\n    }\n}","typeGuard":null,"tryCatchPattern":"loop {\n    match durable_contracts_pin(store, owner) {\n        Ok(pin) => break Ok(pin),\n        Err(e) if e.to_string().contains(\"disappeared during durable contracts scan\") && retries < 3 => {\n            retries += 1;\n            std::thread::sleep(Duration::from_millis(200 * retries));\n        },\n        Err(e) => break Err(e),\n    }\n}","preventionTips":["Never run capsule uninstall/prune concurrently with contracts refresh on the same store","Check disk space and store permissions; interrupted writes cause missing packages","Repair or reinstall capsules whose registry entries lack durable packages","Hold an exclusive store lock for the duration of the scan"],"tags":["consistency","concurrency","registry","race-condition"],"backgroundTag":"internal-invariant-violation","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}