{"record":{"id":"032d61e6733140fc","repo":"astrid-runtime/astrid","slug":"capsule-changed-while-activation-was-in-progr","errorCode":null,"errorMessage":"capsule '{}' changed while activation was in progress","messagePattern":"capsule '(.+?)' changed while activation was in progress","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/capsule_materialization.rs","lineNumber":222,"sourceCode":"            .map_err(|error| anyhow::anyhow!(\"materialize durable capsule package: {error:#}\"))?;\n        let bound_manifest = astrid_capsule::discovery::load_manifest(&target.join(\"Capsule.toml\"))\n            .map_err(|error| anyhow::anyhow!(error))?;\n        self.verify_published_materialization(target, principal, &bound_manifest, snapshot)?;\n        Ok(bound_manifest)\n    }\n\n    /// Recheck the immutable publication after taking activation locks.\n    #[cfg(not(all(target_arch = \"wasm32\", target_os = \"unknown\")))]\n    pub(crate) fn confirm_published_materialization(\n        &self,\n        dir: &Path,\n        principal: &astrid_core::principal::PrincipalId,\n        manifest: &astrid_capsule_types::manifest::CapsuleManifest,\n        snapshot: &astrid_storage::CapsulePackageSnapshot,\n    ) -> anyhow::Result<()> {\n        let current = self.published_capsule_snapshot(principal, manifest)?;\n        if current.as_ref() != Some(snapshot) {\n            anyhow::bail!(\n                \"capsule '{}' changed while activation was in progress\",\n                manifest.package.name\n            );\n        }\n        self.verify_published_materialization(dir, principal, manifest, snapshot)\n    }\n}\n","sourceCodeStart":204,"sourceCodeEnd":230,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/capsule_materialization.rs#L204-L230","documentation":"This error means the published capsule snapshot on disk no longer matches the snapshot the kernel captured when materialization/activation began. The library throws it in confirm_published_materialization as a compare-and-swap style consistency check: between reading the snapshot and confirming activation, the durable published state changed, so activating against the stale snapshot would be unsafe. It is deliberately a hard bail to prevent activating a capsule based on outdated materialized state.","triggerScenarios":"confirm_published_materialization is called (directly via load_capsule, or via prepare_runtime_replacement); it re-reads published_capsule_snapshot(principal, manifest) and the result differs from the snapshot passed in — e.g. a concurrent republish/overwrite of the same capsule, a registry sync updating the digest, or a second activation racing the first.","commonSituations":"Two processes or kernels activating the same capsule version concurrently; an operator re-publishing a capsule tag (mutable tag) while a deployment is in flight; a CI pipeline pushing a new digest for the same package mid-deploy; stale cached snapshot reused after the registry was updated.","solutions":["Re-read the capsule snapshot (load_capsule / fetch latest) and retry the materialization with the fresh snapshot","Ensure only one activation per capsule runs at a time (serialize load_capsule / prepare_runtime_replacement calls with a lock)","If a mutable tag was republished, pin the capsule to an immutable digest and re-run activation","Check for background registry-sync jobs or other kernels writing to the published capsule location and stop them during deploys"],"exampleFix":"// before: reusing a snapshot captured earlier\nlet snapshot = cached_snapshot.clone();\nkernel.confirm_published_materialization(&principal, &manifest, &snapshot).await?;\n\n// after: re-read the current snapshot, retry on concurrent change\nlet snapshot = kernel.published_capsule_snapshot(&principal, &manifest)\n    .await?\n    .ok_or_else(|| anyhow::anyhow!(\"capsule not published\"))?;\nmatch kernel.confirm_published_materialization(&principal, &manifest, &snapshot).await {\n    Ok(()) => {},\n    Err(e) if e.to_string().contains(\"changed while activation\") => {\n        // serialize with a lock and retry once with fresh state\n        let _guard = activation_lock.lock().await;\n        let fresh = kernel.published_capsule_snapshot(&principal, &manifest).await?;\n        kernel.confirm_published_materialization(&principal, &manifest, &fresh).await?;\n    }\n    Err(e) => return Err(e),\n}","handlingStrategy":"retry","validationCode":"let current = kernel.published_capsule_snapshot(&principal, &manifest).await?;\nif current.as_ref() != Some(&snapshot) {\n    // refresh snapshot before calling confirm_published_materialization\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"changed while activation was in progress\") => retry_with_fresh_snapshot().await?,\n    other => other?,\n}","preventionTips":["Serialize activations per capsule with a mutex/lock","Pin capsules to immutable digests instead of mutable tags","Avoid republishing capsule tags while deployments are in flight","Re-read the snapshot immediately before confirming activation"],"tags":["concurrency","state-mismatch","capsule-activation","retryable"],"backgroundTag":"conflicting-config-options","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"}