{"record":{"id":"496619554644f538","repo":"astrid-runtime/astrid","slug":"legacy-capsule-entry-is-not-a-regular-directory","errorCode":null,"errorMessage":"legacy capsule entry is not a regular directory: {}","messagePattern":"legacy capsule entry is not a regular directory: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-capsule-install/src/storage/migration.rs","lineNumber":122,"sourceCode":"            return Ok(LegacyCapsuleMigrationReport::default());\n        },\n        Err(error) => return Err(error).with_context(|| format!(\"inspect {}\", native.display())),\n    };\n    if metadata.file_type().is_symlink() || !metadata.is_dir() {\n        bail!(\n            \"legacy capsule directory is not a regular directory: {}\",\n            native.display()\n        );\n    }\n    astrid_core::platform_fs::verify_no_redirects(&native)\n        .with_context(|| format!(\"verify legacy capsule root {}\", native.display()))?;\n    let mut children = read_dir_sorted(&native)?;\n    let mut report = LegacyCapsuleMigrationReport::default();\n    let registry = store.capsules();\n    let owner = StateOwner::Principal(uid);\n    for (target, target_metadata) in children.drain(..) {\n        if target_metadata.file_type().is_symlink() || !target_metadata.is_dir() {\n            bail!(\n                \"legacy capsule entry is not a regular directory: {}\",\n                target.display()\n            );\n        }\n        let id = target\n            .file_name()\n            .and_then(|name| name.to_str())\n            .ok_or_else(|| anyhow::anyhow!(\"legacy capsule entry has a non-UTF-8 name\"))?;\n        let manifest = astrid_capsule::discovery::load_manifest(&target.join(\"Capsule.toml\"))\n            .with_context(|| format!(\"read legacy capsule manifest {id}\"))?;\n        if manifest.package.name != id {\n            bail!(\n                \"legacy capsule directory {id} does not match manifest id {}\",\n                manifest.package.name\n            );\n        }\n        let meta_bytes = fs::read(target.join(\"meta.json\"))\n            .with_context(|| format!(\"read legacy capsule metadata {id}\"))?;","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/storage/migration.rs#L104-L140","documentation":"While enumerating children of the legacy native capsule directory, each entry must be a regular directory representing one capsule. If an entry is a symlink or a non-directory file, migration stops with this error rather than guessing how to migrate it.","triggerScenarios":"A symlink inside the native capsules directory (e.g. one capsule symlinked to a dev checkout), or a stray file (log, .DS_Store-like artifact, tarball) sitting directly in the capsules root.","commonSituations":"Developers symlink their in-development capsule into the installed capsules directory; downloads or editors leave loose files in the capsules root; corrupted partial installs.","solutions":["Remove the symlink or stray file from the legacy capsules directory","Move the real capsule directory to the expected location instead of linking it","Delete broken/incomplete capsule entries that are not valid directories","Run migration again after the capsules root contains only regular directories"],"exampleFix":"# before\n~/.astrid/native/my-capsule -> ~/dev/my-capsule (symlink)\n# after\ncp -a ~/dev/my-capsule ~/.astrid/native/my-capsule  # real directory","handlingStrategy":"validation","validationCode":"fn capsules_root_clean(p: &Path) -> Result<(), String> {\n    for entry in std::fs::read_dir(p).map_err(|e| e.to_string())? {\n        let e = entry.map_err(|e| e.to_string())?;\n        let ft = e.file_type().map_err(|e| e.to_string())?;\n        if ft.is_symlink() || !ft.is_dir() {\n            return Err(format!(\"{} is not a regular directory\", e.path().display()));\n        }\n    }\n    Ok(())\n}","typeGuard":"fn is_regular_dir_entry(p: &std::path::Path) -> bool {\n    std::fs::symlink_metadata(p)\n        .map(|md| md.is_dir() && !md.file_type().is_symlink())\n        .unwrap_or(false)\n}","tryCatchPattern":"if let Err(e) = migrate_native_capsules(home, store) {\n    if e.to_string().contains(\"entry is not a regular directory\") {\n        eprintln!(\"remove symlinks/stray files from the capsules directory, then retry\");\n    } else { return Err(e); }\n}","preventionTips":["Never symlink individual capsules into the installed capsules directory","Keep loose files (archives, notes) out of the capsules root","Clean broken/partial installs before migrating","Inspect the capsules directory with ls -la before migrating"],"tags":["filesystem","migration","symlink","path-validation"],"backgroundTag":"path-is-not-a-directory","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"}