{"record":{"id":"e84b1a78f8a3e0c7","repo":"nikivdev/code","slug":"relative-path-must-not-be-absolute","errorCode":null,"errorMessage":"Relative path must not be absolute.","messagePattern":"Relative path must not be absolute\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/code.rs","lineNumber":808,"sourceCode":"    }\n\n    Ok(())\n}\n\nfn normalize_path(path: &str) -> Result<PathBuf> {\n    let expanded = config::expand_path(path);\n    let canonical = expanded.canonicalize().unwrap_or(expanded);\n    Ok(canonical)\n}\n\nfn normalize_relative_path(path: &str) -> Result<PathBuf> {\n    let trimmed = path.trim();\n    if trimmed.is_empty() {\n        bail!(\"Relative path cannot be empty.\");\n    }\n    let rel = PathBuf::from(trimmed);\n    if rel.is_absolute() {\n        bail!(\"Relative path must not be absolute.\");\n    }\n    for component in rel.components() {\n        if matches!(component, std::path::Component::ParentDir) {\n            bail!(\"Relative path must not contain '..'.\");\n        }\n    }\n    Ok(rel)\n}\n\nfn move_dir(from: &Path, to: &Path) -> Result<()> {\n    match fs::rename(from, to) {\n        Ok(()) => Ok(()),\n        Err(err) => {\n            if is_cross_device(&err) {\n                copy_dir_all(from, to)?;\n                fs::remove_dir_all(from)\n                    .with_context(|| format!(\"failed to remove {}\", from.display()))?;\n                Ok(())","sourceCodeStart":790,"sourceCodeEnd":826,"githubUrl":"https://github.com/nikivdev/code/blob/a747e741ae92c09071d0ae946ab48488adcff1ce/src/code.rs#L790-L826","documentation":"normalize_relative_path enforces that the supplied path is relative: it rejects any value starting with `/` (or a platform root/drive prefix) via rel.is_absolute(). The library joins the sanitized path onto its own project root, so an absolute path would escape the intended root and write outside the managed directory.","triggerScenarios":"Calling new_project or migrate_project with a relative-path argument like `/tmp/project`, `C:\\dev\\project`, or `~/project` (tilde is not expanded and, after expansion, would be absolute).","commonSituations":"Users pasting absolute paths from their file manager; scripts using `$HOME/...` where a relative name was expected; Windows drive-letter paths leaking into cross-platform scripts; unexpanded `~` assumptions.","solutions":["Pass only the folder name or a relative subpath, e.g. `\"projects/foo\"` instead of `/home/me/projects/foo`.","Strip the known root prefix in your script: `rel=\"${abs#$ROOT/}\"` before calling.","Do not rely on `~` expansion — pass a relative name or expand it yourself and then convert to relative.","Use Path::strip_prefix (or equivalent) to convert an absolute path to one relative to your base directory."],"exampleFix":"// before\nnew_project(\"/home/alice/work/demo\") // absolute → rejected\n// after\nnew_project(\"work/demo\") // relative to the tool's root","handlingStrategy":"validation","validationCode":"let rel = input.trim();\nif Path::new(rel).is_absolute() {\n    return Err(format!(\"expected a relative path, got: {}\", rel).into());\n}","typeGuard":"fn is_relative_path(s: &str) -> bool { Path::new(s.trim()).is_relative() }","tryCatchPattern":"match new_project(&rel) {\n    Err(e) if e.to_string().contains(\"Relative path must not be absolute\") => {\n        eprintln!(\"Pass a path relative to the tool's root, not an absolute one.\");\n    }\n    other => other?,\n}","preventionTips":["Document and enforce that this argument is a name/subpath, not a full path.","Convert absolute inputs with strip_prefix against your base directory.","Do not rely on `~` or `$HOME` expansion in this argument."],"tags":["path-validation","input-validation","cli"],"backgroundTag":"absolute-path-not-allowed","analyzedSha":"a747e741ae92c09071d0ae946ab48488adcff1ce","analyzedAt":"2026-09-01T22:43:55.719Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}