xai-org/grok-build · error · std::io::Error
workflow path has no parent
Error message
workflow path has no parent
What it means
atomic_write needs the parent directory of the destination path to create the temp file and rename. If path.parent() returns None (the path is a bare file name with no directory component), it returns InvalidInput with this message.
Source
Thrown at crates/codegen/xai-grok-shell/src/session/workflow/store.rs:372
}
fn atomic_write_new(path: &Path, bytes: &[u8]) -> io::Result<()> {
if path.exists() {
return Err(io::Error::new(
io::ErrorKind::AlreadyExists,
format!("immutable workflow file already exists: {}", path.display()),
));
}
atomic_write(path, bytes, false)
}
fn atomic_write_replace(path: &Path, bytes: &[u8]) -> io::Result<()> {
atomic_write(path, bytes, true)
}
fn atomic_write(path: &Path, bytes: &[u8], replace: bool) -> io::Result<()> {
let parent = path.parent().ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "workflow path has no parent")
})?;
std::fs::create_dir_all(parent)?;
let file_name = path
.file_name()
.and_then(|name| name.to_str())
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "workflow path is not UTF-8"))?;
let tmp = parent.join(format!(
".{file_name}.{}.{}.tmp",
std::process::id(),
uuid::Uuid::now_v7().simple()
));
let result = (|| {
let mut file = std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&tmp)?;
file.write_all(bytes)?;
file.sync_all()?;View on GitHub (pinned to bc7f02eddd)
Solutions
- Always pass fully-qualified paths that include the parent directory (e.g. run_dir.join("runs.json"))
- Fix callers to build paths from the configured runs root, not bare names
- Verify path construction with .parent().is_some() before calling store write APIs
Example fix
// before
let path = Path::new("runs.json"); // no parent
// after
let path = Path::new(RUNS_ROOT).join("run-abc").join("runs.json"); Defensive patterns
Strategy: validation
Validate before calling
fn writable_path(p: &std::path::Path) -> bool {
p.parent().map(|parent| !parent.as_os_str().is_empty()).unwrap_or(false)
} Type guard
fn with_parent(p: &std::path::Path) -> Option<&std::path::Path> {
p.parent().filter(|parent| !parent.as_os_str().is_empty())
} Try / catch
let path = build_full_path(&run_dir, "runs.json"); assert!(path.parent().is_some(), "workflow paths must include a parent dir"); atomic_write_replace(&path, &bytes)?;
Prevention
- Always join artifact names onto the runs root: runs_root.join(run_id).join(name)
- Never pass bare file names or Path::file_name() results as destinations
- Add a debug assertion that destination paths are absolute or root-relative
- Cover path construction with unit tests using Path::new("file.json") as a negative case
When it happens
Trigger: Passing a relative bare name like "runs.json" (no directory) to atomic_write via atomic_write_new/atomic_write_replace; constructing a path with Path::new(file_name) instead of joining it to the runs dir.
Common situations: Refactor changed an absolute path into a bare file name; a config value holding just a filename was passed where a full path is required; misuse of Path::file_name output fed back as a path.
Related errors
- workflow path is not UTF-8
- target has no parent
- path contains NUL
- trust store path has no parent
- Local marketplace path not found (or is not a directory): {}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/c1b62f0374d0369a.
Report an issue: GitHub.