xai-org/grok-build · error · io::Error
resources state has no parent directory
Error message
resources state has no parent directory
What it means
publish_durable refuses to write the resources state when the target path has no parent directory (a bare filename or empty parent), returning InvalidInput with "resources state has no parent directory". Without an explicit parent, the temporary file would land in the server's current working directory, shared by every session — a correctness hazard, so bare filenames are rejected outright.
Source
Thrown at crates/codegen/xai-grok-tools/src/persistence.rs:341
async fn replace_state_path(path: &Path, tmp_path: &Path) -> io::Result<()> {
if path.is_dir() {
tracing::warn!(
"Resources state path {:?} is a directory — removing before write",
path
);
tokio::fs::remove_dir_all(path).await?;
}
tokio::fs::rename(tmp_path, path).await
}
#[cfg(not(windows))]
async fn publish_durable(path: &Path, tmp_path: &Path) -> io::Result<()> {
// A bare filename has an empty parent, so the write would land in the server's own directory, shared by every session.
let parent = path
.parent()
.filter(|p| !p.as_os_str().is_empty())
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"resources state has no parent directory",
)
})?;
Self::replace_state_path(path, tmp_path).await?;
tokio::fs::File::open(parent).await?.sync_all().await
}
#[cfg(windows)]
async fn publish_durable(path: &Path, tmp_path: &Path) -> io::Result<()> {
use windows::Win32::Storage::FileSystem::MoveFileExW;
use windows::core::PCWSTR;
if path.is_dir() {
tokio::fs::remove_dir_all(path).await?;
}
let from = Self::windows_extended_path(tmp_path)?;
let to = Self::windows_extended_path(path)?;View on GitHub (pinned to bc7f02eddd)
Solutions
- Always build the state path as an absolute, per-session path (e.g. /sessions/<id>/resources.json).
- Join the filename with the session's directory before handing it to the persistence layer.
- Validate configured paths at startup (reject bare filenames early with a clear config error).
- Use std::fs::canonicalize or path.parent() checks in setup code to assert a parent exists.
Example fix
// before
let path = PathBuf::from("resources.json");
// after
let path = session_dir.join("resources.json"); Defensive patterns
Strategy: validation
Validate before calling
fn state_path_has_parent(p: &Path) -> bool {
p.parent().map(|p| !p.as_os_str().is_empty()).unwrap_or(false)
} Try / catch
match persistence.save_and_flush(snapshot).await {
Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => {
bail!("misconfiguration: resources state path must include a directory, got {path:?}")
}
other => other.map_err(Into::into),
} Prevention
- Build state paths as session_dir.join(filename), never bare filenames.
- Validate configured paths at startup with a parent-directory check.
- Prefer absolute paths for per-session state files.
- Add a unit test asserting the path builder always yields a parent.
When it happens
Trigger: Constructing the persistence path from a bare filename like Path::new("state.json") instead of a full per-session path, then triggering a durable publish (save_and_flush) — the tmp file path.with_extension("json.tmp") would resolve against the process cwd.
Common situations: Configuring a session/state path without a directory component; path built by joining an empty string; tests passing a relative bare name; a refactor that dropped the session-directory prefix.
Related errors
- The change was applied, but Doctor could not verify `{}` in
- no .git file or directory found at {}
- local workspace cwd must exist and be canonicalizable: {}: {
- local workspace cwd must be an existing directory: {}
- {LOCAL_WORKSPACE_HOME_DENIED}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/d29f81bfdf5b836e.
Report an issue: GitHub.