{"record":{"id":"a51dff0f2e9d58c5","repo":"AprilNEA/OpenLogi","slug":"asset-must-be-a-non-symlink-directory","errorCode":null,"errorMessage":"{asset} must be a non-symlink directory","messagePattern":"(.+?) must be a non-symlink directory","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/openlogi-cli/src/cmd/fixture/contribute.rs","lineNumber":486,"sourceCode":"    if state.fixture_id != args.id\n        || state.profile_id != format!(\"{}-profile\", args.id)\n        || state.profile_name != args.name\n    {\n        bail!(\"--id and --name must match the in-progress contribution\");\n    }\n    Ok(())\n}\n\nfn read_json<T: serde::de::DeserializeOwned>(path: &Path, asset: &str) -> Result<T> {\n    let bytes = fs::read(path).with_context(|| format!(\"could not read {asset}\"))?;\n    serde_json::from_slice(&bytes).with_context(|| format!(\"could not parse {asset}\"))\n}\n\nfn require_directory(path: &Path, asset: &str) -> Result<()> {\n    let metadata =\n        fs::symlink_metadata(path).with_context(|| format!(\"could not inspect {asset}\"))?;\n    if metadata.file_type().is_symlink() || !metadata.is_dir() {\n        bail!(\"{asset} must be a non-symlink directory\");\n    }\n    Ok(())\n}\n\nfn require_regular_file(path: &Path, asset: &str) -> Result<()> {\n    let metadata =\n        fs::symlink_metadata(path).with_context(|| format!(\"could not inspect {asset}\"))?;\n    if metadata.file_type().is_symlink() || !metadata.is_file() {\n        bail!(\"{asset} must be a non-symlink regular file\");\n    }\n    Ok(())\n}\n\nfn reject_symlink(path: &Path, asset: &str) -> Result<()> {\n    match fs::symlink_metadata(path) {\n        Ok(metadata) if metadata.file_type().is_symlink() => {\n            bail!(\"{asset} output must not be a symlink\")\n        }","sourceCodeStart":468,"sourceCodeEnd":504,"githubUrl":"https://github.com/AprilNEA/OpenLogi/blob/e846e6f4b4405e33bd6a9aaf949a482ce34cb6d8/crates/openlogi-cli/src/cmd/fixture/contribute.rs#L468-L504","documentation":"`require_directory` demands that a given asset path exists, is a real directory, and is not a symbolic link, using `symlink_metadata` so symlinks are detected even when they point at directories. This protects the fixture layout from symlink-based substitutions that could break integrity guarantees of published fixtures.","triggerScenarios":"Any of run, publish_manifest_and_finish, or validate_resumable_layout checks an asset path that (a) is a symlink to a directory, or (b) is not a directory at all (a regular file, or nonexistent — the latter surfaces as the wrapped `could not inspect {asset}` context error instead).","commonSituations":"Checking out fixture trees where directories were replaced by symlinks (e.g. shared fixtures via ln -s); pointing an asset path at a file; a packaging step that replaced a directory with a link.","solutions":["Replace the symlink with a real directory: `rm <asset> && mkdir <asset>` (copy contents back if needed).","Point the asset path at the actual directory rather than a link to it.","Verify with `ls -la` / `test -d <asset> && ! test -L <asset>` before running the command."],"exampleFix":"// before (assets/cases -> ../shared/cases symlink)\nln -s ../shared/cases out/mx-master/cases\n// after\nrm out/mx-master/cases && mkdir out/mx-master/cases && cp ../shared/cases/* out/mx-master/cases/","handlingStrategy":"validation","validationCode":"[ -d \"$ASSET\" ] && [ ! -L \"$ASSET\" ] || { echo \"$ASSET must be a real directory\" >&2; exit 1; }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Check out fixture trees without symlink-based directory sharing.","Use real directories (or bind mounts for build setups), not ln -s.","Audit fixture layout with `find . -type l` before contributing."],"tags":["filesystem","symlink","validation"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"e846e6f4b4405e33bd6a9aaf949a482ce34cb6d8","analyzedAt":"2026-09-13T03:07:16.451Z","contentChangedAt":"2026-09-13T03:07:16.451Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}