xai-org/grok-build · error
destination has no filename: {}
Error message
destination has no filename: {} What it means
On Windows, windows_replace_exe derives the backup name (dest.old) from the destination's file name; if the destination path has no final component (e.g. a drive root like C:\ or a path ending in ..) it cannot form a backup name and throws this error. It guards the self-replacement logic from silently corrupting the install path.
Source
Thrown at crates/codegen/xai-grok-update/src/auto_update.rs:2081
///
/// On Windows the kernel prevents writes to a running executable but allows
/// renames. If a direct copy fails with a sharing violation, this renames
/// `dest` aside and copies `src` into the freed path. If the copy then
/// fails, the rename is rolled back to avoid a broken install.
///
/// The aside target is normally `<dest>.old`, but a leftover `.old` can
/// itself still be a running image (the session that was live during the
/// previous update keeps executing the renamed-aside file), and a running
/// image can neither be deleted nor rename-replaced. In that case `dest` is
/// renamed to a unique `<dest>.old.{pid}-{seq}.old` sibling instead, so a
/// locked leftover can never block the update. All `.old` leftovers are
/// swept best-effort at the start of each cycle; still-locked ones survive
/// until a later update runs after those processes exit.
#[cfg(windows)]
async fn windows_replace_exe(src: &std::path::Path, dest: &std::path::Path) -> Result<()> {
let file_name = dest
.file_name()
.ok_or_else(|| anyhow::anyhow!("destination has no filename: {}", dest.display()))?
.to_string_lossy();
let old = dest.with_file_name(format!("{file_name}.old"));
sweep_old_exe_backups(&old).await;
match tokio::fs::copy(src, dest).await {
Ok(_) => return Ok(()),
// ERROR_SHARING_VIOLATION (32) / ERROR_ACCESS_DENIED (5): exe is
// locked by a running process. Fall through to rename-and-replace.
Err(e) if matches!(e.raw_os_error(), Some(32) | Some(5)) => {
tracing::debug!("exe locked, falling back to rename: {e}");
}
Err(e) => return Err(e.into()),
}
// A .old that survived the sweep is locked; renaming onto it would need
// to delete-replace it and fail, so divert to a guaranteed-free name.
let old_is_free = matches!(View on GitHub (pinned to bc7f02eddd)
Solutions
- Point the update destination at a concrete executable file path (e.g. C:\\Users\\me\\bin\\grok.exe), not a directory or root
- Normalize the path (canonicalize / dunce::canonicalize) so it resolves to a real file name
- Fix install_dir configuration so dest includes the executable file name
Example fix
// before
let dest = std::path::Path::new("C:\\>"); // drive root — no file name
windows_replace_exe(&src, dest).await?;
// after
let dest = std::path::Path::new("C:\\Users\\me\\bin\\grok.exe");
windows_replace_exe(&src, dest).await?; Defensive patterns
Strategy: validation
Validate before calling
if dest.file_name().is_none() {
anyhow::bail!("destination must be a file path, got: {}", dest.display());
} Type guard
fn has_file_name(p: &std::path::Path) -> bool {
p.file_name().map_or(false, |n| !n.is_empty())
} Try / catch
match windows_replace_exe(&src, &dest).await {
Err(e) if e.to_string().starts_with("destination has no filename") => {
eprintln!("fix install_dir to point at the executable file");
}
result => result?,
} Prevention
- Always pass a full executable file path as dest (e.g. ...\\grok.exe)
- Canonicalize install_dir before constructing dest
- Never point update destinations at drive roots or directories
When it happens
Trigger: Calling windows_replace_exe (via the install/replace flow) with a dest path that has no file_name — drive roots ("C:\\"), trailing separators resolving to nothing, or empty/oddly-normalized paths.
Common situations: Misconfigured install directory pointing at a drive root; programmatically-built paths ending in a separator or ".."; a user config pointing the update destination at a mount root.
Related errors
- path contains NUL
- not a git repository: {}
- cannot rename locked executable {}: {e} Close all running gr
- target has no parent
- overlay mount delegation not supported by this delegate
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/026cf8fa7bf99f6d.
Report an issue: GitHub.