{"record":{"id":"9a146a258f07befb","repo":"Hmbown/CodeWhale","slug":"immutable-artifact-handle-already-contains-different-bytes","errorCode":null,"errorMessage":"immutable artifact handle already contains different bytes","messagePattern":"immutable artifact handle already contains different bytes","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/artifacts.rs","lineNumber":177,"sourceCode":"    relative_path: &Path,\n    content: &[u8],\n) -> io::Result<PathBuf> {\n    let absolute_path =\n        session_artifact_absolute_path(session_id, relative_path).ok_or_else(|| {\n            io::Error::new(io::ErrorKind::InvalidInput, \"invalid session artifact path\")\n        })?;\n    let destination = open_session_relative(session_id, relative_path, true)?;\n    match destination.publish(content) {\n        Ok(()) => {}\n        Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {\n            use std::io::Read;\n            let mut existing = Vec::new();\n            destination\n                .open_file()?\n                .take(content.len() as u64 + 1)\n                .read_to_end(&mut existing)?;\n            if existing != content {\n                return Err(io::Error::new(\n                    io::ErrorKind::AlreadyExists,\n                    \"immutable artifact handle already contains different bytes\",\n                ));\n            }\n        }\n        Err(err) => return Err(err),\n    }\n    Ok(absolute_path)\n}\n\n/// The same confined session directory for mutable sidecars and immutable\n/// artifacts. The saved-session owner remains responsible for session state.\npub(crate) fn open_session_relative(\n    session_id: &str,\n    relative_path: &Path,\n    create: bool,\n) -> io::Result<crate::fleet::files::WorkspaceFile> {\n    session_artifact_absolute_path(session_id, relative_path).ok_or_else(|| {","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/artifacts.rs#L159-L195","documentation":"Immutable artifact handles are write-once: if the destination already exists with different bytes, the write is refused to guarantee replay safety. When `publish` reports AlreadyExists, the existing file is read and compared; a mismatch produces this AlreadyExists io::Error, while identical bytes are treated as a successful idempotent write.","triggerScenarios":"Writing to an artifact handle (via `write_session_artifact_immutable`, evidence publication, or `export`) where the target file already exists but its content differs from the bytes being published — e.g. reusing an artifact id for new content.","commonSituations":"Replaying a session after the artifact was legitimately regenerated with different output; a hash/id collision or a bug reusing ids across runs; manual tampering with the artifact file; exporting to a path that already holds unrelated data.","solutions":["Use a fresh artifact id (or content-derived id) for the new content instead of reusing an existing handle.","If the existing content should win, skip the write when content is unchanged and treat AlreadyExists-with-same-bytes as success (the API already does this).","Inspect the existing file: if it's corrupted/tampered, delete it deliberately and rewrite.","Fix id-generation logic so the same logical artifact always produces identical bytes."],"exampleFix":"// before\nlet id = format!(\"run-{}\", run_counter); // counter reused across reruns\nwrite_session_artifact_immutable(session, &id, \"json\", &new_bytes)?;\n\n// after\nlet id = format!(\"run-{}-{}\", run_id, sha256_hex(&new_bytes));\nwrite_session_artifact_immutable(session, &id, \"json\", &new_bytes)?;","handlingStrategy":"try-catch","validationCode":"fn id_matches_content(id: &str, content: &[u8]) -> bool {\n    // content-derived ids avoid reusing a handle for different bytes\n    let digest = sha256_hex(content);\n    id.contains(&digest[..8])\n}","typeGuard":null,"tryCatchPattern":"match write_session_relative_immutable(session, rel, bytes) {\n    Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists\n        && e.to_string().contains(\"different bytes\") => {\n        // handle exists with other content: mint a new id or skip\n    }\n    other => other?,\n}","preventionTips":["Derive artifact ids from content hashes so identical content maps to identical ids.","Never reuse an artifact id across logically different runs.","Treat idempotent re-publish of identical bytes as success, not an error.","Don't hand-edit published immutable artifacts."],"tags":["artifacts","immutability","conflict","data-integrity"],"backgroundTag":"file-already-exists","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}