{"record":{"id":"026cf8fa7bf99f6d","repo":"xai-org/grok-build","slug":"destination-has-no-filename","errorCode":null,"errorMessage":"destination has no filename: {}","messagePattern":"destination has no filename: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-update/src/auto_update.rs","lineNumber":2081,"sourceCode":"///\n/// On Windows the kernel prevents writes to a running executable but allows\n/// renames. If a direct copy fails with a sharing violation, this renames\n/// `dest` aside and copies `src` into the freed path. If the copy then\n/// fails, the rename is rolled back to avoid a broken install.\n///\n/// The aside target is normally `<dest>.old`, but a leftover `.old` can\n/// itself still be a running image (the session that was live during the\n/// previous update keeps executing the renamed-aside file), and a running\n/// image can neither be deleted nor rename-replaced. In that case `dest` is\n/// renamed to a unique `<dest>.old.{pid}-{seq}.old` sibling instead, so a\n/// locked leftover can never block the update. All `.old` leftovers are\n/// swept best-effort at the start of each cycle; still-locked ones survive\n/// until a later update runs after those processes exit.\n#[cfg(windows)]\nasync fn windows_replace_exe(src: &std::path::Path, dest: &std::path::Path) -> Result<()> {\n    let file_name = dest\n        .file_name()\n        .ok_or_else(|| anyhow::anyhow!(\"destination has no filename: {}\", dest.display()))?\n        .to_string_lossy();\n    let old = dest.with_file_name(format!(\"{file_name}.old\"));\n\n    sweep_old_exe_backups(&old).await;\n\n    match tokio::fs::copy(src, dest).await {\n        Ok(_) => return Ok(()),\n        // ERROR_SHARING_VIOLATION (32) / ERROR_ACCESS_DENIED (5): exe is\n        // locked by a running process. Fall through to rename-and-replace.\n        Err(e) if matches!(e.raw_os_error(), Some(32) | Some(5)) => {\n            tracing::debug!(\"exe locked, falling back to rename: {e}\");\n        }\n        Err(e) => return Err(e.into()),\n    }\n\n    // A .old that survived the sweep is locked; renaming onto it would need\n    // to delete-replace it and fail, so divert to a guaranteed-free name.\n    let old_is_free = matches!(","sourceCodeStart":2063,"sourceCodeEnd":2099,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-update/src/auto_update.rs#L2063-L2099","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet dest = std::path::Path::new(\"C:\\\\>\"); // drive root — no file name\nwindows_replace_exe(&src, dest).await?;\n// after\nlet dest = std::path::Path::new(\"C:\\\\Users\\\\me\\\\bin\\\\grok.exe\");\nwindows_replace_exe(&src, dest).await?;","handlingStrategy":"validation","validationCode":"if dest.file_name().is_none() {\n    anyhow::bail!(\"destination must be a file path, got: {}\", dest.display());\n}","typeGuard":"fn has_file_name(p: &std::path::Path) -> bool {\n    p.file_name().map_or(false, |n| !n.is_empty())\n}","tryCatchPattern":"match windows_replace_exe(&src, &dest).await {\n    Err(e) if e.to_string().starts_with(\"destination has no filename\") => {\n        eprintln!(\"fix install_dir to point at the executable file\");\n    }\n    result => result?,\n}","preventionTips":["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"],"tags":["windows","filesystem","path","validation"],"backgroundTag":"invalid-destination-path","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}