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

  1. Use a fresh run id (e.g. uuid v7) for each register call
  2. Remove the stale run directory only if it is confirmed dead/orphaned
  3. Check for existing run id (load_workflow_runs_sync) before registering
  4. 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

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


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