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

  1. Join the target onto the intended directory before saving: base_dir.join(file_name)
  2. Validate the path with target.parent().is_some() before calling
  3. Canonicalize/resolve relative paths against the project root early
  4. 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

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


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/a048162566817377. Report an issue: GitHub.