rust-lang/rust · error
Error writing pre-lto-bitcode file `{}`: {}
Error message
Error writing pre-lto-bitcode file `{}`: {} What it means
Raised in the Thin-LTO branch of execute_optimize_work_item when rustc has just serialized a module to a thin bitcode buffer and fails to persist it to the incremental-compilation session directory. The panic happens in fs::write to a path computed from incr_comp_session_dir joined with pre_lto_bitcode_filename(module.name). It indicates an I/O failure writing the pre-LTO .bc artifact that downstream Thin-LTO steps will need to read back.
Source
Thrown at compiler/rustc_codegen_ssa/src/back/write.rs:859
// If we're doing some form of incremental LTO then we need to be sure to
// save our module to disk first.
let bitcode = if cgcx.module_config.emit_pre_lto_bc {
let filename = pre_lto_bitcode_filename(&module.name);
cgcx.incr_comp_session_dir.as_ref().map(|path| path.join(&filename))
} else {
None
};
match lto_type {
ComputedLtoType::No => {
let module = B::codegen(cgcx, &prof, &shared_emitter, module, &cgcx.module_config);
WorkItemResult::Finished(module)
}
ComputedLtoType::Thin => {
let thin_buffer = B::serialize_module(module.module_llvm, true);
if let Some(path) = bitcode {
fs::write(&path, thin_buffer.data()).unwrap_or_else(|e| {
panic!("Error writing pre-lto-bitcode file `{}`: {}", path.display(), e);
});
}
WorkItemResult::NeedsThinLto(module.name, thin_buffer)
}
ComputedLtoType::Fat => match bitcode {
Some(path) => {
let buffer = B::serialize_module(module.module_llvm, false);
fs::write(&path, buffer.data()).unwrap_or_else(|e| {
panic!("Error writing pre-lto-bitcode file `{}`: {}", path.display(), e);
});
WorkItemResult::NeedsFatLto(FatLtoInput::Serialized {
name: module.name,
bitcode_path: path,
})
}
None => WorkItemResult::NeedsFatLto(FatLtoInput::InMemory(module)),
},
}View on GitHub (pinned to 22057b88b0)
Solutions
- Free disk space and inodes on the volume holding target/
- Stop concurrent processes touching target/incremental (e.g. another cargo invocation or a cleaner)
- Move target to a local non-network directory: CARGO_TARGET_DIR=/local/target
- Disable incremental compilation if the env is hostile: CARGO_INCREMENTAL=0
- On Windows, exclude the target dir from antivirus scanning
Example fix
# before (disk full / shared FS) CARGO_INCREMENTAL=1 cargo build # after CARGO_INCREMENTAL=0 cargo build
Defensive patterns
Strategy: try-catch
Validate before calling
use std::path::{Path, PathBuf};
use std::fs;
fn preflight_write_pre_lto(path: &Path) -> std::io::Result<()> {
if let Some(parent) = path.parent() {
if !parent.as_os_str().is_empty() && !parent.exists() {
fs::create_dir_all(parent)?;
}
// probe writability without committing the real file
let probe = parent.join(format!(".{}.writeprobe", path.file_name().and_then(|s| s.to_str()).unwrap_or("tmp")));
fs::write(&probe, b"")?;
fs::remove_file(&probe)?;
}
if path.exists() && fs::metadata(path)?.permissions().readonly() {
return Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"pre-lto-bitcode target is read-only",
));
}
let free = fs2::free_space(path.parent().unwrap_or(Path::new(".")))
.ok();
if let Some(b) = free {
if b < 256 * 1024 * 1024 {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"insufficient free space for pre-lto-bitcode write",
));
}
}
Ok(())
}
// before invoking codegen: preflight_write_pre_lto(&out_path)?; Type guard
// Write errors here are thin wrappers around std::io::Error; narrow on kind.
fn is_pre_lto_write_error(e: &std::io::Error) -> bool {
matches!(
e.kind(),
std::io::ErrorKind::PermissionDenied
| std::io::ErrorKind::NotFound
| std::io::ErrorKind::StorageFull
| std::io::ErrorKind::ReadOnlyFilesystem
| std::io::ErrorKind::QuotaExceeded
)
} Try / catch
match write_pre_lto_bitcode(&path, &bitcode) {
Ok(()) => {},
Err(ref e) if e.kind() == std::io::ErrorKind::StorageFull
|| e.kind() == std::io::ErrorKind::QuotaExceeded => {
// non-retryable resource exhaustion; surface to caller, do NOT silently drop
return Err(format!("disk/quota full writing {}: {}", path.display(), e).into());
}
Err(ref e) if e.kind() == std::io::ErrorKind::PermissionDenied
|| e.kind() == std::io::ErrorKind::ReadOnlyFilesystem => {
return Err(format!("permission denied writing {}: {}", path.display(), e).into());
}
Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => {
// single bounded retry; Interrupted is explicitly documented as retryable
write_pre_lto_bitcode(&path, &bitcode)
.map_err(|e| format!("retry failed writing {}: {}", path.display(), e))?;
}
Err(e) => return Err(format!("error writing pre-lto-bitcode {}: {}", path.display(), e).into()),
} Prevention
- Write pre-lto-bitcode to a temp file in the same directory as the final path, fsync, then rename atomically; avoids partial-file corruption on crash.
- Create the destination directory with create_dir_all before codegen starts, not mid-write.
- Reserve disk headroom (e.g. >=2x expected bitcode size) before running parallel codegen units.
- Do not run the writer with read-only mounts or snapshot directories as output targets.
- Handle EINTR explicitly: std I/O can surface Interrupted and a single retry is correct.
When it happens
Trigger: cgcx.module_config.emit_pre_lto_bc is true (incremental + Thin LTO), lto_type is ComputedLtoType::Thin, and fs::write(&path, thin_buffer.data()) returns Err. Concrete causes: target/incremental dir deleted or moved mid-build, disk full, out of inodes, path on a read-only mount, antivirus locking the file on Windows, or a permission drop between rustc spawns.
Common situations: CI disk pressure; cleaning target/ while a build runs; building into an NFS/SMB share; Windows Defender scanning .bc files; cargo workspace where one member wipes the shared target dir; out-of-space on small cloud VMs.
Related errors
- failed to open LTO bitcode file `{}`: {}
- couldn't open rlib
- couldn't map rlib
- failed to mmap LTO bitcode file `{}`: {}
- Failed to remove {path}: {err}
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/9a5ef3558b23c1ae.json.
Report an issue: GitHub.