unicity-aos/aos-ce · error · std::io::Error
temporary product manifest path must be a regular file
Error message
temporary product manifest path must be a regular file
What it means
During ensure_unicity_ce_manifest, the temporary file path <manifest>.toml.tmp is checked with symlink_metadata before writing; if it exists as a symlink or non-regular file the function returns InvalidInput "temporary product manifest path must be a regular file". A normal leftover regular temp file is simply removed; only dangerous non-regular entries are refused, guarding against symlink attacks on the temp path.
Solutions
- Inspect `ls -la <manifest>.toml.tmp` and remove the offending symlink/special file, then retry.
- Clear the distributions directory of stale temp files after abnormal terminations.
- Run the process under a dedicated user so a hostile local user cannot plant entries in the private directory.
Example fix
// before (shell, failing state) ls -la $ROOT/distributions/product.toml.tmp # lrwxrwxrwx -> /etc/shadow // after rm -f $ROOT/distributions/product.toml.tmp && retry ensure_unicity_ce_manifest
Defensive patterns
Strategy: validation
Validate before calling
fn tmp_path_is_safe(tmp: &Path) -> Result<(), String> {
if let Ok(m) = std::fs::symlink_metadata(tmp) {
if m.file_type().is_symlink() || !m.is_file() {
return Err(format!("{:?} is a symlink or special file; delete it before writing", tmp));
}
}
Ok(())
} Type guard
fn tmp_is_absent_or_regular(path: &Path) -> bool {
match std::fs::symlink_metadata(path) {
Ok(m) => m.is_file() && !m.file_type().is_symlink(),
Err(_) => true, // absent is fine
}
} Try / catch
if let Err(e) = runtime.ensure_unicity_ce_manifest() {
if e.to_string().contains("temporary product manifest") {
let _ = std::fs::remove_file(manifest_path.with_extension("toml.tmp"));
// retry once
} else { return Err(e.into()); }
} Prevention
- Clean up stale .tmp files after crashes or abnormal termination of the daemon.
- Run under a dedicated user with a private runtime directory so hostile local users cannot plant symlinks at predictable temp paths.
- Avoid hand-editing files inside the distributions directory while the daemon runs.
When it happens
Trigger: Calling ensure_unicity_ce_manifest when <manifest>.toml.tmp exists in the distributions directory as a symlink, directory, fifo, or other non-regular file.
Common situations: A previous crashed/aborted write left a temp entry that something replaced with a symlink; a malicious actor pre-planted a symlink at the predictable .tmp path; manual cleanup accidentally created a directory there.
Related errors
- product manifest path must be a regular file
- AOS capsule directory must be a real directory
- AOS managed path must be a real directory
- AOS capsule directory contains a non-regular entry
- Unicity AOS health service must bind to 127.0.0.1
AI-assisted analysis of unicity-aos/aos-ce@f6f22024fb (2026-09-13).
Data as JSON: /api/errors/edb9dbc68d8b8b9e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/unicity-aos-bootstrap/src/lib.rs:214
Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_file() => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"product manifest path must be a regular file",
));
}
Ok(_) if fs::read(&path)?.as_slice() == manifest.as_bytes() => return Ok(path),
Ok(_) => {}
Err(error) if error.kind() == io::ErrorKind::NotFound => {}
Err(error) => return Err(error),
}
self.ensure_layout()?;
create_private_dir(&self.root.join("distributions"))?;
let parent = path.parent().expect("manifest path has a parent");
create_private_dir(parent)?;
let temporary = path.with_extension("toml.tmp");
if let Ok(metadata) = fs::symlink_metadata(&temporary) {
if metadata.file_type().is_symlink() || !metadata.is_file() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"temporary product manifest path must be a regular file",
));
}
fs::remove_file(&temporary)?;
}
fs::write(&temporary, manifest)?;
set_private_file_permissions(&temporary)?;
fs::rename(&temporary, &path)?;
Ok(path)
}
/// Initialize the trusted CE system fleet before the runtime performs its
/// daemon-backed grant preflight.
///
/// A completely fresh Astrid home has no capsule capable of accepting CLI
/// connections. Astrid installs through the daemon in bounded batches.
/// Resume partial batches after the kernel's rate-limit window, leavingView on GitHub (pinned to f6f22024fb)