BloopAI/vibe-kanban · error · ContainerError
Failed to get relative path for {source_file:?}: {e}
Error message
Failed to get relative path for {source_file:?}: {e} What it means
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).
Source
Thrown at crates/local-deployment/src/copy.rs:96
source_root: &Path,
target_root: &Path,
seen: &mut HashSet<PathBuf>,
) -> Result<bool, ContainerError> {
let canonical_source = source_root.canonicalize()?;
let canonical_file = source_file.canonicalize()?;
// Validate path is within source_dir
if !canonical_file.starts_with(canonical_source) {
return Err(ContainerError::Other(anyhow!(
"File {source_file:?} is outside project directory"
)));
}
if !seen.insert(canonical_file.clone()) {
return Ok(false);
}
let relative_path = source_file.strip_prefix(source_root).map_err(|e| {
ContainerError::Other(anyhow!(
"Failed to get relative path for {source_file:?}: {e}"
))
})?;
let target_file = target_root.join(relative_path);
if target_file.exists() {
return Ok(false);
}
if let Some(parent) = target_file.parent()
&& !parent.exists()
{
fs::create_dir_all(parent)?;
}
fs::copy(source_file, &target_file)?;
Ok(true)View on GitHub (pinned to 4deb7eca8f)
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.
Example fix
// before let relative_path = source_file.strip_prefix(source_root).map_err(...)?; // after let canonical_root = source_root.canonicalize()?; let relative_path = canonical_file.strip_prefix(&canonical_root).map_err(...)?;
Defensive patterns
Strategy: validation
Validate before calling
let root = source_root.canonicalize()?;
if source_file.strip_prefix(&root).is_err() {
return Err(anyhow!("{:?} not under canonical root {:?}", source_file, root));
} Type guard
fn has_root_prefix(root: &Path, file: &Path) -> bool {
file.starts_with(root)
} Try / catch
match source_file.strip_prefix(source_root) {
Ok(rel) => rel,
Err(e) => {
tracing::error!(file=?source_file, root=?source_root, "strip_prefix failed: {e}");
return Err(e.into());
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- cannot atomically write path without a parent directory
- Copy project files timed out after 30s
- Copy files task failed: {e}
- File {source_file:?} is outside project directory
- Migration failed: {}
AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29).
Data as JSON: /api/errors/0c98191f3bd65df7.
Report an issue: GitHub.