{"record":{"id":"4a521ac81dd9b8ca","repo":"astrid-runtime/astrid","slug":"legacy-env-source-is-not-a-regular-file","errorCode":null,"errorMessage":"legacy env source is not a regular file: {}","messagePattern":"legacy env source is not a regular file: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/env_import.rs","lineNumber":38,"sourceCode":"    store: &RuntimePrincipalStore,\n    bindings: &[(PrincipalId, PrincipalUid)],\n    snapshots: &BTreeMap<String, SourceIdentity>,\n    host_secret_source: &SourceIdentity,\n) -> io::Result<()> {\n    let handle = tokio::runtime::Handle::current();\n    for (alias, uid) in bindings {\n        let owner = astrid_storage::StateOwner::Principal(*uid);\n        let summaries = store.capsules().list(&owner).map_err(storage_io)?;\n        let env_root = home.principal_home(alias).env_dir();\n        let secret_root = home.secrets_dir().join(alias.as_str());\n        // Unknown capsule-specific files cannot be assigned safely.  This is\n        // deliberately a hard dependency rather than an alias-based guess.\n        if path_exists(&env_root)? {\n            let mut entries = fs::read_dir(&env_root).map_err(io::Error::other)?;\n            while let Some(entry) = entries.next().transpose().map_err(io::Error::other)? {\n                let metadata = fs::symlink_metadata(entry.path()).map_err(io::Error::other)?;\n                if !metadata.is_file() {\n                    return Err(io::Error::new(\n                        io::ErrorKind::InvalidData,\n                        format!(\n                            \"legacy env source is not a regular file: {}\",\n                            entry.path().display()\n                        ),\n                    ));\n                }\n            }\n        }\n        for summary in summaries {\n            let capsule = summary.id();\n            let env = env_root.join(format!(\"{capsule}.env.json\"));\n            let secret = secret_root.join(capsule);\n            require_scope_matches_ledger(\n                snapshots,\n                &format!(\"principal:{uid}:env:{capsule}\"),\n                &env,\n            )?;","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/env_import.rs#L20-L56","documentation":"During legacy layout migration, each entry under the legacy env root must be a regular file (env or secret payload). Symlinks and directories are rejected with `InvalidData` because the migration deliberately hard-depends on real files rather than guessing through aliases.","triggerScenarios":"Calling `migrate_legacy_layout` → `import_env_and_secrets` when `read_dir` on the env root yields an entry whose `symlink_metadata` is not a plain file (symlink, directory, fifo, etc.).","commonSituations":"Users symlinked legacy secret files into a dotfiles manager (e.g. GNU Stow/chezmoi); a directory was created where a secret file should be; leftover temp directories in the legacy env root.","solutions":["Replace the symlinked/directory entry with a real regular file copy.","Move unrelated directories out of the legacy env root.","Re-run the migration after the env root contains only regular files."],"exampleFix":"# before\n.env -> ~/dotfiles/.env  (symlink)\n# after\nrm .env && cp ~/dotfiles/.env .env","handlingStrategy":"validation","validationCode":"fn legacy_env_ready(root: &std::path::Path) -> bool {\n    std::fs::read_dir(root).map(|rd| rd.filter_map(Result::ok).all(|e| {\n        std::fs::symlink_metadata(e.path()).map(|m| m.is_file() && !m.file_type().is_symlink()).unwrap_or(false)\n    })).unwrap_or(false)\n}","typeGuard":"fn is_regular_file(p: &std::path::Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| m.is_file() && !m.file_type().is_symlink()).unwrap_or(false)\n}","tryCatchPattern":"match migrate_legacy_layout(...) {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidData\n        && e.to_string().contains(\"not a regular file\") => {\n        eprintln!(\"replace symlinks/dirs in legacy env root with real files\");\n        return Err(e.into());\n    }\n    other => other,\n}","preventionTips":["Keep legacy env roots free of symlinks; copy files instead of linking.","Audit the legacy env root with `find . -type l` before migrating.","Exclude legacy secret files from dotfile-stowing tools."],"tags":["filesystem","symlink","migration"],"backgroundTag":"incompatible-source-type","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}