{"record":{"id":"0c98191f3bd65df7","repo":"BloopAI/vibe-kanban","slug":"failed-to-get-relative-path-for-source-file","errorCode":null,"errorMessage":"Failed to get relative path for {source_file:?}: {e}","messagePattern":"Failed to get relative path for (.+?): (.+?)","errorType":"exception","errorClass":"ContainerError","httpStatus":null,"severity":"error","filePath":"crates/local-deployment/src/copy.rs","lineNumber":96,"sourceCode":"    source_root: &Path,\n    target_root: &Path,\n    seen: &mut HashSet<PathBuf>,\n) -> Result<bool, ContainerError> {\n    let canonical_source = source_root.canonicalize()?;\n    let canonical_file = source_file.canonicalize()?;\n    // Validate path is within source_dir\n    if !canonical_file.starts_with(canonical_source) {\n        return Err(ContainerError::Other(anyhow!(\n            \"File {source_file:?} is outside project directory\"\n        )));\n    }\n\n    if !seen.insert(canonical_file.clone()) {\n        return Ok(false);\n    }\n\n    let relative_path = source_file.strip_prefix(source_root).map_err(|e| {\n        ContainerError::Other(anyhow!(\n            \"Failed to get relative path for {source_file:?}: {e}\"\n        ))\n    })?;\n\n    let target_file = target_root.join(relative_path);\n\n    if target_file.exists() {\n        return Ok(false);\n    }\n\n    if let Some(parent) = target_file.parent()\n        && !parent.exists()\n    {\n        fs::create_dir_all(parent)?;\n    }\n    fs::copy(source_file, &target_file)?;\n\n    Ok(true)","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/crates/local-deployment/src/copy.rs#L78-L114","documentation":"After the containment check passes, copy_single_file calls strip_prefix(source_root) to compute the file's path relative to the project root. If strip_prefix fails this error is thrown. Given the preceding starts_with(canonical_source) check, this should be nearly impossible, but it can still occur when source_file was constructed inconsistently (e.g. different separators or a non-normalized root).","triggerScenarios":"source_root passed with a trailing separator or non-normalized form ('./project/' vs 'project') while source_file was built from the normalized root, making strip_prefix fail despite containment passing via canonical paths.","commonSituations":"Programmatically joining paths with mixed separators on Windows; roots built with './' prefixes; callers passing symlinked root paths while files are enumerated from the canonical root.","solutions":["Normalize source_root before use (strip trailing separators, use the same PathBuf used to enumerate files).","Compute relative paths from the canonicalized root (canonical_source) rather than the raw source_root.","Log both source_root and source_file and verify they share the exact same prefix.","Use pathdiff::diff_paths as a fallback when strip_prefix fails."],"exampleFix":"// before\nlet relative_path = source_file.strip_prefix(source_root).map_err(...)?;\n// after\nlet canonical_root = source_root.canonicalize()?;\nlet relative_path = canonical_file.strip_prefix(&canonical_root).map_err(...)?;","handlingStrategy":"validation","validationCode":"let root = source_root.canonicalize()?;\nif source_file.strip_prefix(&root).is_err() {\n    return Err(anyhow!(\"{:?} not under canonical root {:?}\", source_file, root));\n}","typeGuard":"fn has_root_prefix(root: &Path, file: &Path) -> bool {\n    file.starts_with(root)\n}","tryCatchPattern":"match source_file.strip_prefix(source_root) {\n    Ok(rel) => rel,\n    Err(e) => {\n        tracing::error!(file=?source_file, root=?source_root, \"strip_prefix failed: {e}\");\n        return Err(e.into());\n    }\n}","preventionTips":["Always construct source_file from the exact same root PathBuf used for strip_prefix.","Normalize paths (canonicalize) before prefix operations.","Avoid trailing separators and './' prefixes in roots.","Add a unit test for relative-path computation on your platform."],"tags":["path","filesystem","strip-prefix"],"backgroundTag":"invalid-path-prefix","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}