Hmbown/CodeWhale · error · std::io::Error

AlreadyExists

AlreadyExists

Error message

immutable artifact handle already contains different bytes

What it means

write_session_relative_immutable publishes write-once artifacts: if the target already exists and its bytes differ from `content`, it fails closed with AlreadyExists; identical bytes are an idempotent Ok. This guarantees a replayed or concurrent session cannot silently change what an earlier artifact handle points to — the artifact contract is one immutable payload per (session, relative path).

Source

Thrown at crates/tui/src/artifacts.rs:181

/// A duplicate replay with identical bytes is idempotent; a different payload
/// for the same relative path fails closed.
pub fn write_session_relative_immutable(
    session_id: &str,
    relative_path: &Path,
    content: &[u8],
) -> io::Result<PathBuf> {
    let absolute_path =
        session_artifact_absolute_path(session_id, relative_path).ok_or_else(|| {
            io::Error::new(io::ErrorKind::InvalidInput, "invalid session artifact path")
        })?;
    if let Some(parent) = absolute_path.parent() {
        std::fs::create_dir_all(parent)?;
    }
    if absolute_path.exists() {
        return if std::fs::read(&absolute_path)? == content {
            Ok(absolute_path)
        } else {
            Err(io::Error::new(
                io::ErrorKind::AlreadyExists,
                "immutable artifact handle already contains different bytes",
            ))
        };
    }
    let file_name = absolute_path
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or("artifact");
    let temp_path = absolute_path.with_file_name(format!(
        ".{file_name}.{}.{}.tmp",
        std::process::id(),
        uuid::Uuid::new_v4()
    ));
    let publish = (|| -> io::Result<()> {
        let mut file = std::fs::OpenOptions::new()
            .write(true)
            .create_new(true)

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Derive artifact ids from content identity (hash) or the unique tool_call_id so different bytes always take different paths
  2. If the existing artifact is stale, delete that session's artifacts directory (or the single file) before re-recording
  3. Make the payload deterministic for replays (strip volatile fields like timestamps before publishing)
  4. Treat the error as a caller-side contract violation: audit who wrote the first payload and why it differs

Example fix

// before
let rel = session_artifact_relative_path(&format!("art_{name}")); // fixed name, changing bytes
let abs = write_session_relative_immutable(sid, &rel, bytes)?; // AlreadyExists

// after: content-addressed id, collisions are now identical bytes
let digest = sha256(&bytes);
let rel = session_artifact_relative_path(&format!("art_{name}_{digest}"));
let abs = write_session_relative_immutable(sid, &rel, bytes)?;
Defensive patterns

Strategy: fallback

Validate before calling

let target = session_artifact_absolute_path(sid, &rel);
if let Some(abs) = &target {
    if let Ok(existing) = std::fs::read(abs) {
        if existing != bytes {
            // choose a new unique relative path now, before the failing write
        }
    }
}

Try / catch

match write_session_relative_immutable(sid, &rel, bytes) {
    Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
        // same path, different bytes: republish under a fresh content-derived id
        // do NOT delete+overwrite in place unless you own the whole session
    }
    other => other?,
}

Prevention

When it happens

Trigger: Two different payloads written under the same session_id + relative_path: a tool regenerating output under a stable artifact name, a resumed session producing divergent content, or two distinct raw ids colliding after sanitize_id_component maps them to the same sanitized name (e.g. 'a/b' and 'a_b').

Common situations: Nondeterministic tool output (timestamps, random ids) reused under a fixed artifact name; replaying an edited session; duplicate tool-call ids from a buggy provider stream.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/25d3aece1d73e271. Report an issue: GitHub.