xai-org/grok-build · error · std::io::Error
immutable workflow file already exists: {}
Error message
immutable workflow file already exists: {} What it means
atomic_write_new is used for immutable files (creating a run's artifacts for the first time). If the destination already exists, it refuses with AlreadyExists and this message instead of overwriting, preserving immutability of registered workflow files.
Source
Thrown at crates/codegen/xai-grok-shell/src/session/workflow/store.rs:358
));
}
let mut bytes = Vec::with_capacity(opened.len() as usize);
file.take(limit.saturating_add(1)).read_to_end(&mut bytes)?;
if bytes.len() as u64 > limit {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"workflow artifact exceeds {limit} bytes: {}",
path.display()
),
));
}
Ok(bytes)
}
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()View on GitHub (pinned to bc7f02eddd)
Solutions
- Use a fresh run id (e.g. uuid v7) for each register call
- Remove the stale run directory only if it is confirmed dead/orphaned
- Check for existing run id (load_workflow_runs_sync) before registering
- Serialize registration through the persistence actor so duplicates cannot race
Example fix
// before
store.register("run-abc", manifest)?; // second call -> already exists
// after
let run_id = uuid::Uuid::now_v7().simple().to_string(); // unique per run
store.register(&run_id, manifest)?; Defensive patterns
Strategy: try-catch
Validate before calling
fn run_already_registered(runs_root: &std::path::Path, id: &str) -> bool {
runs_root.join(id).join("runs.json").exists()
} Try / catch
match store.register(&run_id, manifest) {
Ok(()) => {},
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
// id collision/stale run: pick a new id instead of overwriting
run_id = fresh_run_id();
store.register(&run_id, manifest)?;
}
Err(e) => return Err(e.into()),
} Prevention
- Generate a fresh unique run id per register call (uuid v7 simple)
- Never retry register with the same id after a failure without checking existence
- Clean up orphaned run dirs via an explicit GC step, not blind overwrite
- Funnel registration through the persistence actor to avoid concurrent duplicate creation
When it happens
Trigger: register() called twice with the same run id and revision; a leftover file from a previous failed run occupies the path; register racing another register of the same id.
Common situations: Retry logic re-invoking register after a timeout without checking id uniqueness; stale run directory from a crashed session; collision between two concurrently started runs using the same id.
Related errors
- workflow source already registered: {run_id}
- Failed to set working directory to {:?}: {}
- Failed to load config: {e}
- reading command stdout: {e}
- failed to read {}: {e}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/8a5d0b023c525105.
Report an issue: GitHub.