{"record":{"id":"9de9e88719f8f738","repo":"AprilNEA/OpenLogi","slug":"asset-output-must-not-be-a-symlink","errorCode":null,"errorMessage":"{asset} output must not be a symlink","messagePattern":"(.+?) output must not be a symlink","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/openlogi-cli/src/cmd/fixture/contribute.rs","lineNumber":503,"sourceCode":"    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        }\n        Ok(_) => Ok(()),\n        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),\n        Err(error) => Err(error).with_context(|| format!(\"could not inspect {asset} output\")),\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use openlogi_fixture::CANONICAL_DEVICE_PROFILE_JSON;\n\n    use super::*;\n\n    #[test]\n    fn output_directory_must_match_the_synthetic_id() {\n        let args = ContributeArgs {\n            id: \"fixture-001\".to_string(),\n            name: \"Synthetic mouse\".to_string(),","sourceCodeStart":485,"sourceCodeEnd":521,"githubUrl":"https://github.com/AprilNEA/OpenLogi/blob/e846e6f4b4405e33bd6a9aaf949a482ce34cb6d8/crates/openlogi-cli/src/cmd/fixture/contribute.rs#L485-L521","documentation":"`reject_symlink` ensures an output path that is about to be published is not a symlink; unlike the require_* helpers it tolerates a nonexistent path (NotFound is OK, since publishing will create it) but bails if the path already exists as a symlink, and wraps other inspection errors with `could not inspect {asset} output`.","triggerScenarios":"publish_manifest_and_finish finds an existing symlink at an output location (manifest or state output) while finalizing the contribution. Also fires on any non-NotFound I/O error while inspecting the path.","commonSituations":"A previous tool or setup linked the output file to elsewhere (e.g. symlinked state.json into a synced folder); leftover links from an older workflow.","solutions":["Remove the symlink at the output path (`rm <path>`) and re-run finish so a real file is written.","If the link was intentional (e.g. sync tooling), move the actual data to the output location and delete the link first.","Inspect with `ls -la <path>` to confirm what exists before re-running."],"exampleFix":"// before (state.json -> ~/sync/state.json)\n// after\nrm out/mx-master/state.json && openlogi fixture contribute finish --id mx-master --name \"MX Master\" ...","handlingStrategy":"validation","validationCode":"[ ! -L \"$OUT_PATH\" ] || { echo \"$OUT_PATH must not be a symlink\" >&2; exit 1; }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep output locations free of links from sync/backup tooling.","Remove any stale symlink at the output path before finishing a contribution.","Write outputs inside the fixture directory itself, which the tooling controls."],"tags":["filesystem","symlink","output"],"backgroundTag":"path-traversal-blocked","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"}