{"record":{"id":"ca6d358609206a20","repo":"astrid-runtime/astrid","slug":"legacy-audit-source-is-not-a-regular-directory","errorCode":null,"errorMessage":"legacy audit source is not a regular directory: {}","messagePattern":"legacy audit source is not a regular directory: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs","lineNumber":465,"sourceCode":"    let retired = home.migrations_dir().join(\"audit-principal-home.retired\");\n    let expected = home.principal_home(&PrincipalId::default()).audit_dir();\n    if source != expected {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidInput,\n            \"legacy audit retirement source is outside the default principal audit path\",\n        ));\n    }\n    astrid_core::platform_fs::verify_no_redirects(source.parent().ok_or_else(|| {\n        io::Error::new(\n            io::ErrorKind::InvalidInput,\n            \"legacy audit source has no parent\",\n        )\n    })?)?;\n    astrid_core::platform_fs::ensure_private_directory(&home.migrations_dir())?;\n    astrid_core::platform_fs::verify_no_redirects(&home.migrations_dir())?;\n    match fs::symlink_metadata(source) {\n        Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"legacy audit source is not a regular directory: {}\",\n                    source.display()\n                ),\n            ));\n        },\n        Ok(_) => {\n            if fs::symlink_metadata(&retired).is_ok() {\n                return Err(io::Error::new(\n                    io::ErrorKind::AlreadyExists,\n                    format!(\n                        \"legacy audit retirement is ambiguous: {}\",\n                        retired.display()\n                    ),\n                ));\n            }\n            let root_device = device_id(&fs::symlink_metadata(source)?);","sourceCodeStart":447,"sourceCodeEnd":483,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs#L447-L483","documentation":"retire_legacy_audit_dir refuses to rename the legacy audit source unless it is a plain, non-symlink directory. Using symlink_metadata (which does not follow symlinks), it rejects the path with InvalidData if the entry is a symlink or any non-directory file type, because retiring a redirected or non-directory path could destroy or bypass data outside the expected legacy layout.","triggerScenarios":"Calling migrate_legacy_audit when <principal home>/audit is a symlink (e.g. to another location), a regular file, fifo, socket, or other non-directory entry.","commonSituations":"Users symlinking the audit directory to a bigger disk or dotfiles-managed location; a stale file named 'audit' left where the directory used to be; dotfile managers (stow,chezmoi) replacing the directory with a symlink.","solutions":["Remove the symlink/file at the legacy audit path and replace it with a real directory containing the legacy audit data, then re-run migration.","If the symlink points at the real data, remove the link, move the data back into a plain directory at the expected path, and retry.","Delete an empty or stale non-directory 'audit' entry so migration treats the source as absent and skips retirement."],"exampleFix":"// before: audit is a symlink\nrm ~/ .astrid/principal/audit   # symlink\nln -s /mnt/big/audit ~/.astrid/principal/audit\n// after: real directory\nrm ~/.astrid/principal/audit\nmv /mnt/big/audit ~/.astrid/principal/audit","handlingStrategy":"validation","validationCode":"match std::fs::symlink_metadata(&source) {\n    Ok(m) if m.file_type().is_symlink() || !m.is_dir() => {\n        return Err(format!(\"{} must be a real directory\", source.display()));\n    },\n    Ok(_) | Err(e) if e.kind() == std::io::ErrorKind::NotFound => {},\n    Err(e) => return Err(e.into()),\n}","typeGuard":"fn is_plain_dir(p: &Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| m.is_dir() && !m.file_type().is_symlink()).unwrap_or(false)\n}","tryCatchPattern":"if let Err(e) = migrate_legacy_audit(&home, &source) {\n    if e.kind() == std::io::ErrorKind::InvalidData && e.to_string().contains(\"not a regular directory\") {\n        // replace symlink/file with a real directory, then retry\n    }\n}","preventionTips":["Avoid symlinking ~/.astrid paths via dotfile managers.","Check symlink_metadata (not metadata) on legacy paths before migrating.","Keep legacy audit data in a real directory on the home filesystem."],"tags":["filesystem","symlink","migration","path-validation"],"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-14T05:17:10.506Z"}