GitoxideLabs/gitoxide · error
Removal target ' ' must be contained in boundary
Error message
Removal target '{target}' must be contained in boundary '{boundary}' What it means
gix-fs' bounded directory remover (`gix_fs::dir::remove::new`) deletes `target` but guarantees it never deletes anything at or above `boundary`. It validates up front that `target`'s path starts with `boundary`; if not, it refuses with `InvalidInput` rather than risk deleting outside the allowed region. Paths are not canonicalized for performance, so the check is lexical.
Solutions
- Ensure both paths share the same form (both absolute or both relative) and that `target` literally starts with `boundary`.
- Canonicalize both paths (`std::fs::canonicalize` or `gix_fs` helpers) before constructing the remover.
- Fix the caller logic that computes `target` so it is always inside `boundary`.
Example fix
// before: mixed forms fail the starts_with check
let r = gix_fs::dir::remove::DirRemover::new(Path::new("repo/objects/pack"), Path::new("/abs/repo"))?;
// after: canonicalize both first
let target = std::fs::canonicalize("repo/objects/pack")?;
let boundary = std::fs::canonicalize("/abs/repo")?;
let r = gix_fs::dir::remove::DirRemover::new(&target, &boundary)?; Defensive patterns
Strategy: validation
Validate before calling
let target = std::fs::canonicalize(target)?; let boundary = std::fs::canonicalize(boundary)?; assert!(target.starts_with(&boundary), "target must be inside boundary");
Try / catch
let dir = gix_fs::dir::remove::DirRemover::new(&target, &boundary)
.map_err(|e| anyhow::anyhow!("bad removal target: {e}"))?; Prevention
- Canonicalize paths before constructing the remover — the containment check is lexical.
- Keep target and boundary in the same form (both absolute or both relative).
- Assert containment in tests for any code computing cleanup targets.
When it happens
Trigger: Calling `DirRemover::new(target, boundary)` where `target` is not a lexical descendant of `boundary` — e.g. non-canonicalized paths like `repo/./objects` vs `repo`, or a target from a different tree.
Common situations: Mixing relative and absolute paths; passing symlinked or `..`-containing paths that were never canonicalized; a bug computing the cleanup target during pack/odb garbage collection.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Refusing to read an empty path from
- always with parent directory
- ' ' is not a valid configuration key
- Cannot use iter_v1() on index of type
- Cannot use iter_v2() on index of type
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/62bd4522eea19485.
Report an issue: GitHub.
Appendix: source
Thrown at gix-fs/src/dir/remove.rs:21
/// A special iterator which communicates its operation through results where…
///
/// * `Some(Ok(removed_directory))` is yielded once or more success, followed by `None`
/// * `Some(Err(std::io::Error))` is yielded exactly once on failure.
pub struct Iter<'a> {
cursor: Option<&'a Path>,
boundary: &'a Path,
}
/// Construction
impl<'a> Iter<'a> {
/// Create a new instance that deletes `target` but will stop at `boundary`, without deleting the latter.
/// Returns an error if `boundary` doesn't contain `target`
///
/// **Note** that we don't canonicalize the path for performance reasons.
pub fn new(target: &'a Path, boundary: &'a Path) -> std::io::Result<Self> {
if !target.starts_with(boundary) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!(
"Removal target '{target}' must be contained in boundary '{boundary}'",
target = target.display(),
boundary = boundary.display()
),
));
}
let cursor = if target == boundary {
None
} else if target.exists() {
Some(target)
} else {
None
};
Ok(Iter { cursor, boundary })
}
}View on GitHub (pinned to e73179060b)