astrid-runtime/astrid · error

Workspace capsule directory changed during manifest lookup:

Error message

Workspace capsule directory changed during manifest lookup: {e}

What it means

Secret capsule manifests are resolved by verifying the workspace `capsules` tree multiple times (TOCTOU protection). When a capsule manifest does not exist in the workspace, the code re-verifies the tree before returning `Ok(None)`; if that second verification fails, the capsule directory changed (or was found unsafe) during the lookup and this error is thrown instead of silently returning "not found".

Source

Thrown at crates/astrid-cli/src/commands/secret.rs:133

        .join(capsule.as_str())
        .join("Capsule.toml");
    if principal_manifest.exists() {
        return read_capsule_manifest(&principal_manifest).map(Some);
    }

    let Some(workspace_root) = workspace_root else {
        return Ok(None);
    };
    let workspace = workspace_layout
        .resolve(workspace_root)
        .context("Failed to resolve the selected workspace")?;
    let capsules_dir = workspace
        .verify_tree("capsules")
        .context("Workspace capsule directory is unsafe")?;
    let workspace_manifest = capsules_dir.join(capsule.as_str()).join("Capsule.toml");
    if !workspace_manifest.exists() {
        workspace.verify_tree("capsules").map_err(|e| {
            anyhow::anyhow!("Workspace capsule directory changed during manifest lookup: {e}")
        })?;
        return Ok(None);
    }
    let manifest = read_capsule_manifest(&workspace_manifest)?;
    workspace
        .verify_tree("capsules")
        .context("Workspace capsule directory changed while reading its manifest")?;
    Ok(Some(manifest))
}

#[cfg(test)]
fn read_capsule_manifest(manifest_path: &Path) -> Result<CapsuleManifest> {
    let contents = fs::read_to_string(manifest_path)
        .with_context(|| format!("Failed to read {}", manifest_path.display()))?;
    let manifest: CapsuleManifest = toml::from_str(&contents)
        .with_context(|| format!("Failed to parse {}", manifest_path.display()))?;
    Ok(manifest)
}

View on GitHub (pinned to affd8760f4)

Solutions

  1. Retry the lookup once the workspace is quiescent; the change may be benign concurrent activity.
  2. Inspect the `capsules` directory for unexpected changes (symlinks, new files, permission changes) that violate the tree verification.
  3. Stop concurrent writers (sync clients, build scripts) or exclude the workspace from live syncing during secret operations.
  4. Treat repeated occurrences as a security signal — verify_tree failing twice suggests an unsafe or redirected capsule tree; restore it from a trusted state.

Example fix

// before
let manifest = load_capsule_manifest(&capsule)?; // may fail on concurrent change
// after
let manifest = match load_capsule_manifest(&capsule) {
    Ok(m) => m,
    Err(e) if e.to_string().contains("changed during manifest lookup") => {
        eprintln!("workspace changed; retrying");
        load_capsule_manifest(&capsule)?
    }
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check capsule tree stability before lookup
let before = snapshot_capsules_dir(&workspace)?;
let manifest = load_capsule_manifest(&capsule)?;
let after = snapshot_capsules_dir(&workspace)?;
assert_eq!(before, after, "capsules tree changed during lookup");

Type guard

fn capsules_tree_stable(workspace: &Workspace) -> bool {
    workspace.verify_tree("capsules").is_ok()
}

Try / catch

match load_capsule_manifest(&capsule) {
    Ok(m) => m,
    Err(e) if e.to_string().contains("changed during manifest lookup") => {
        eprintln!("workspace mutated concurrently; retrying");
        load_capsule_manifest(&capsule)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling secret/capsule manifest lookup (e.g. `load_capsule_manifest_from_home_in_workspace` via `load_capsule_manifest_*`) while the workspace `capsules` directory is modified concurrently — files added/removed, symlink redirects introduced, or permission/structure changes that make `verify_tree("capsules")` fail on the second pass.

Common situations: Another process (editor, sync tool, build script) writing to the capsules directory during a secret lookup; a symlink or redirected workspace capsule being swapped in mid-lookup; an attacker-manipulated or corrupted capsules tree; tests that mutate the workspace concurrently.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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