astrid-runtime/astrid · error

lifecycle manifest declares environment state but no durable

Error message

lifecycle manifest declares environment state but no durable principal UID binding was supplied

What it means

run_lifecycle_in_scope executes a capsule lifecycle phase (install/upgrade/etc.) with a scoped runtime. If the manifest declares env state (manifest.env non-empty), a durable principal UID binding (RuntimePrincipalStore) is mandatory, because environment state must be persisted under a durable principal's storage — a plain in-memory KV would silently lose it. The function bails when env state is declared but principal_storage is None.

Source

Thrown at crates/astrid-capsule-install/src/lifecycle.rs:186

        external_bus,
    )
}

#[allow(clippy::too_many_arguments)]
fn run_lifecycle_in_scope(
    target_dir: &Path,
    wasm_bytes: Vec<u8>,
    manifest: &CapsuleManifest,
    _home: Option<&AstridHome>,
    target_principal: &PrincipalId,
    principal_uid: Option<astrid_core::identity::PrincipalUid>,
    principal_storage: Option<RuntimePrincipalStore>,
    phase: LifecyclePhase,
    previous_version: Option<&str>,
    external_bus: Option<EventBus>,
) -> anyhow::Result<()> {
    if principal_storage.is_none() && !manifest.env.is_empty() {
        anyhow::bail!(
            "lifecycle manifest declares environment state but no durable principal UID binding was supplied"
        );
    }
    let kv_store: Arc<dyn astrid_storage::KvStore> = principal_storage.as_ref().map_or_else(
        || Arc::new(astrid_storage::MemoryKvStore::new()) as Arc<dyn astrid_storage::KvStore>,
        |store| store.kv(),
    );
    let capsule_id = manifest.package.name.clone();
    let kv = astrid_storage::ScopedKvStore::new(
        Arc::clone(&kv_store),
        lifecycle_kv_namespace(target_principal, &capsule_id),
    )
    .context("failed to create scoped KV store")?;
    let event_bus = external_bus.unwrap_or_else(|| EventBus::with_capacity(128));

    // Reuse the current tokio runtime when there is one (CLI's
    // `#[tokio::main]`, kernel handler thread). Only build a new one
    // for standalone/test contexts.

View on GitHub (pinned to affd8760f4)

Solutions

  1. Open/attach a RuntimePrincipalStore (durable principal UID binding) and pass it so the lifecycle can persist env state.
  2. If the capsule genuinely needs no persistent env state, remove the `env` section from the lifecycle manifest.
  3. Use run_lifecycle_for_principal (or _with_storage) instead of the storage-less run_lifecycle path for capsules with env declarations.

Example fix

// before
run_lifecycle(&capsule, &manifest, LifecyclePhase::Install, None)?; // env declared, no store
// after
let store = RuntimePrincipalStore::open(&home, &principal_uid)?;
run_lifecycle_for_principal(&capsule, &manifest, LifecyclePhase::Install, Some(store), None)?;
Defensive patterns

Strategy: validation

Validate before calling

// caller-side pre-check:
if !manifest.env.is_empty() && principal_storage.is_none() {
    return Err(anyhow::anyhow!(
        "manifest declares env state; open a RuntimePrincipalStore first"));
}

Try / catch

match run_lifecycle(...) {
    Err(e) if e.to_string().contains("no durable principal UID binding") => {
        eprintln!("capsule declares env state; use run_lifecycle_for_principal with an open store");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling run_lifecycle / run_lifecycle_for_principal paths that construct run_lifecycle_in_scope with principal_storage = None while the capsule's lifecycle manifest has a non-empty `env` section — e.g. an install flow that forgot to open/attach the principal store, or running a manifest that newly added env declarations without migrating the runner.

Common situations: Adding `env` to a capsule's lifecycle manifest while the host integration still invokes the lifecycle without a principal store; a CLI/daemon code path that lazily skips opening RuntimePrincipalStore for capsules believed to be stateless; test harnesses running lifecycle scripts without storage wiring.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/49fc0e020788ac8e. Report an issue: GitHub.