xai-org/grok-build · error · std::io::Error
target has no parent
Error message
target has no parent
What it means
atomic_create_new writes a new workflow file by creating a temp file in the target's parent directory; if Path::parent() returns None the path has no parent component (e.g. a bare relative filename like "wf.json" or a root path), and the library raises InvalidInput "target has no parent". It cannot stage a temp sibling file without a directory.
Source
Thrown at crates/codegen/xai-grok-shell/src/session/workflow/registry.rs:616
}
let canonical = dunce::canonicalize(dir).map_err(|error| ResolveError::Io {
path: dir.display().to_string(),
error: error.to_string(),
})?;
if !canonical.starts_with(&root) {
return Err(ResolveError::UntrustedPath {
path: dir.display().to_string(),
reason: "save directory escaped project root".into(),
});
}
Ok(())
}
fn atomic_create_new(target: &Path, bytes: &[u8]) -> io::Result<()> {
let parent = target
.parent()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "target has no parent"))?;
let temp = parent.join(format!(".workflow-{}.tmp", uuid::Uuid::now_v7().simple()));
let result = (|| {
let mut options = std::fs::OpenOptions::new();
options.write(true).create_new(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o600).custom_flags(libc::O_NOFOLLOW);
}
let mut file = options.open(&temp)?;
file.write_all(bytes)?;
file.sync_all()?;
#[cfg(unix)]
std::fs::hard_link(&temp, target)?;
#[cfg(windows)]
atomic_rename_noreplace_windows(&temp, target)?;
if let Ok(dir) = std::fs::File::open(parent) {
let _ = dir.sync_all();View on GitHub (pinned to bc7f02eddd)
Solutions
- Join the target onto the intended directory before saving: base_dir.join(file_name)
- Validate the path with target.parent().is_some() before calling
- Canonicalize/resolve relative paths against the project root early
- Reject empty or root-only path components at config-parse time
Example fix
// before
let target = Path::new(&workflow_id).with_extension("json");
registry.save_project_workflow(&target, &bytes)?;
// after
let target = workflows_dir.join(workflow_id).with_extension("json");
assert!(target.parent().is_some(), "target must have a parent dir");
registry.save_project_workflow(&target, &bytes)?; Defensive patterns
Strategy: validation
Validate before calling
fn ensure_parent(target: &Path) -> io::Result<()> {
if target.parent().is_none() {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "target has no parent"));
}
std::fs::create_dir_all(target.parent().unwrap())
} Type guard
fn has_parent(target: &Path) -> bool {
target.parent().is_some()
} Try / catch
match registry.save_project_workflow(&target, &bytes) {
Err(e) if e.kind() == io::ErrorKind::InvalidInput && e.to_string().contains("no parent") => {
eprintln!("workflow path must include a directory: {target:?}");
}
other => other?,
} Prevention
- Always build workflow paths as dir.join(id), never from a bare id string
- Validate user/config-supplied paths contain a directory component at load time
- Canonicalize relative paths against the project root early
- Create parent directories with create_dir_all before saving
When it happens
Trigger: Calling save_project_workflow with a target path that has no parent component — a bare filename relative to nothing, or a path consisting only of a root (e.g. "/" on unix).
Common situations: Constructing the workflow path from an unvalidated user/config string; forgetting to join the registry/scratch directory onto a workflow id; passing Path::new("workflow.json") instead of dir.join("workflow.json").
Related errors
- not a git repository: {}
- destination has no filename: {}
- failed to replace {}: {e}
- InvalidInput
- summary.json is empty (0 bytes): {}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/a048162566817377.
Report an issue: GitHub.