xai-org/grok-build · error

InvalidInput

InvalidInput

Error message

path has no parent

What it means

`atomic_write` in the subagent module persists `meta.json`/`output.json` by writing a same-directory temp file and renaming. If `path.parent()` returns `None` — the path is bare/root-like with no directory component — the function fails with `std::io::Error` of kind `InvalidInput` and message 'path has no parent', because the temp file must be created in the same directory as the target for the rename to be atomic.

Solutions

  1. Always pass an absolute path under the subagent's directory (e.g. `<session_dir>/meta.json`)
  2. Join the bare filename onto a real base directory before writing
  3. Pre-validate `path.parent().is_some()` in the caller and error out early with a better message

Example fix

// before
atomic_write(Path::new("meta.json"), &contents)?;
// after
let path = session_dir.join("meta.json");
atomic_write(&path, &contents)?;
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_writable(p: &Path) -> Result<(), String> {
    if p.parent().is_none() {
        return Err(format!("{}: join the filename onto a real directory", p.display()));
    }
    Ok(())
}

Type guard

fn has_parent_dir(p: &Path) -> bool { p.parent().is_some() }

Try / catch

match atomic_write(&path, &contents) {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput && e.to_string() == "path has no parent" => {
        eprintln!("meta/output path must include its directory");
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling `atomic_write` (via subagent meta/output persistence) with a path like `Path::new("meta.json")` or `Path::new("/")` that has no parent directory component.

Common situations: A bug where the subagent directory prefix was empty or dropped before joining the filename; constructing the path from a mis-parsed config value; tests passing a bare filename directly.

Related errors


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

Appendix: source

Thrown at crates/codegen/xai-grok-shell/src/agent/subagent/mod.rs:2294

            isolation_mode: isolation_mode.map(str::to_string),
            depth,
            started_at: meta.started_at.to_rfc3339(),
            completed_at: meta.completed_at.map(|t| t.to_rfc3339()),
            status: meta.status.clone(),
            duration_ms: meta.duration_ms,
            tool_calls: meta.tool_calls,
            turns: meta.turns,
            error: meta.error.clone(),
            fork_copy_error: meta.fork_copy_error.clone(),
            resumed_from: meta.resumed_from.clone(),
        }
    }
}
/// Write via a same-directory temp file and rename, so a crash mid-write
/// cannot leave a torn `meta.json` or `output.json`.
fn atomic_write(path: &Path, contents: &str) -> std::io::Result<()> {
    let parent = path.parent().ok_or_else(|| {
        std::io::Error::new(std::io::ErrorKind::InvalidInput, "path has no parent")
    })?;
    std::fs::create_dir_all(parent)?;
    let tmp = tempfile::NamedTempFile::new_in(parent)?;
    std::fs::write(tmp.path(), contents)?;
    tmp.persist(path)?;
    Ok(())
}
/// Write `meta.json`. Returns `true` on success so callers on the resume-pointer
/// path can gate worktree disposal on a durable write.
fn write_subagent_meta(dir: &Path, meta: &SubagentMeta) -> bool {
    let json = match serde_json::to_string_pretty(meta) {
        Ok(json) => json,
        Err(e) => {
            tracing::warn!(error = %e, "failed to serialize subagent meta");
            return false;
        }
    };
    if let Err(e) = atomic_write(&dir.join("meta.json"), &json) {

View on GitHub (pinned to bc7f02eddd)