AprilNEA/OpenLogi · error
must be a non-symlink directory
Error message
{asset} must be a non-symlink directory What it means
`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.
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.
Example fix
// before (assets/cases -> ../shared/cases symlink) ln -s ../shared/cases out/mx-master/cases // after rm out/mx-master/cases && mkdir out/mx-master/cases && cp ../shared/cases/* out/mx-master/cases/
Defensive patterns
Strategy: validation
Validate before calling
[ -d "$ASSET" ] && [ ! -L "$ASSET" ] || { echo "$ASSET must be a real directory" >&2; exit 1; } Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- must be a non-symlink regular file
- output must not be a symlink
- fixture structure verification failed
- fixture structure verification failed
- already exists but is not an in-progress OpenLogi…
AI-assisted analysis of AprilNEA/OpenLogi@e846e6f4b4 (2026-09-13).
Data as JSON: /api/errors/a51dff0f2e9d58c5.
Report an issue: GitHub.
Appendix: source
Thrown at crates/openlogi-cli/src/cmd/fixture/contribute.rs:486
if state.fixture_id != args.id
|| state.profile_id != format!("{}-profile", args.id)
|| state.profile_name != args.name
{
bail!("--id and --name must match the in-progress contribution");
}
Ok(())
}
fn read_json<T: serde::de::DeserializeOwned>(path: &Path, asset: &str) -> Result<T> {
let bytes = fs::read(path).with_context(|| format!("could not read {asset}"))?;
serde_json::from_slice(&bytes).with_context(|| format!("could not parse {asset}"))
}
fn require_directory(path: &Path, asset: &str) -> Result<()> {
let metadata =
fs::symlink_metadata(path).with_context(|| format!("could not inspect {asset}"))?;
if metadata.file_type().is_symlink() || !metadata.is_dir() {
bail!("{asset} must be a non-symlink directory");
}
Ok(())
}
fn require_regular_file(path: &Path, asset: &str) -> Result<()> {
let metadata =
fs::symlink_metadata(path).with_context(|| format!("could not inspect {asset}"))?;
if metadata.file_type().is_symlink() || !metadata.is_file() {
bail!("{asset} must be a non-symlink regular file");
}
Ok(())
}
fn reject_symlink(path: &Path, asset: &str) -> Result<()> {
match fs::symlink_metadata(path) {
Ok(metadata) if metadata.file_type().is_symlink() => {
bail!("{asset} output must not be a symlink")
}View on GitHub (pinned to e846e6f4b4)