astrid-runtime/astrid · error

invalid capsule ID

Error message

invalid capsule ID: {e}

What it means

run_lifecycle_in_scope validates the caller-supplied capsule ID string by constructing a typed astrid_capsule::capsule::CapsuleId before executing any lifecycle hook. If the string does not satisfy the CapsuleId format rules (e.g. empty or containing forbidden characters), the whole lifecycle run fails immediately. This fail-fast check prevents an invalid identifier from being propagated into storage namespaces, secrets, or KV paths.

Solutions

  1. Print the exact capsule ID string being passed (debug log) and compare against the ID reported by the install/registry listing commands.
  2. Validate the string locally with CapsuleId::new before calling run_lifecycle to get an early, clear failure.
  3. Look up the correct canonical ID from the installed capsule metadata (meta.json) instead of using a directory name or label.
  4. If the ID looks valid but is rejected, upgrade astrid-capsule crates so the producer and validator agree on the ID format.

Example fix

// before
run_lifecycle(&config, "My Capsule!", Lifecycle::Install)?;
// after
let capsule_id = CapsuleId::new("my-capsule".to_string())?;
run_lifecycle(&config, capsule_id.as_str(), Lifecycle::Install)?;
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_valid_capsule_id(id: &str) -> anyhow::Result<()> {
    astrid_capsule::capsule::CapsuleId::new(id.to_string())
        .map(|_| ())
        .map_err(|e| anyhow::anyhow!("capsule id {id:?} invalid: {e}"))
}

Prevention

When it happens

Trigger: Calling run_lifecycle, run_lifecycle_for_principal, or run_lifecycle_for_principal_with_storage with a capsule_id string that CapsuleId::new rejects: empty string, whitespace, non-canonical characters, or an ID copied from a display name rather than the canonical ID.

Common situations: Hand-editing config or CLI args and typo-ing the capsule ID; passing a human-readable capsule name where a canonical ID is expected; older capsules installed before an ID-format change being re-run through lifecycle commands.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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

Appendix: source

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

    .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.
    let (owned_rt, handle) = if let Ok(handle) = tokio::runtime::Handle::try_current() {
        (None, handle)
    } else {
        let rt = tokio::runtime::Builder::new_multi_thread()
            .enable_all()
            .build()
            .context("failed to build tokio runtime for lifecycle")?;
        let handle = rt.handle().clone();
        (Some(rt), handle)
    };

    let capsule_id_owned = astrid_capsule::capsule::CapsuleId::new(capsule_id.clone())
        .map_err(|e| anyhow::anyhow!("invalid capsule ID: {e}"))?;
    let secret_namespace = if let Some(uid) = principal_uid {
        astrid_storage::env::principal_secret_namespace(uid, &capsule_id)
    } else {
        // A no-env preview lifecycle may use an isolated host-only scope. It
        // is never an authority source and cannot be selected for manifests
        // that declare environment state (the guard above fails closed).
        astrid_storage::env::system_secret_namespace("lifecycle-ephemeral")
    };
    let secret_scope = astrid_storage::ScopedKvStore::new(Arc::clone(&kv_store), secret_namespace)
        .context("failed to create lifecycle secret control scope")?;
    let secret_store = astrid_storage::build_secret_store(
        &format!("{capsule_id}:{target_principal}"),
        secret_scope,
        handle.clone(),
    );
    // Lifecycle hooks use the same host-owned typed environment projection as
    // steady-state invocations.  Loading this snapshot here is what makes
    // daemon-staged `--var` values visible before an install/upgrade hook

View on GitHub (pinned to affd8760f4)