{"record":{"id":"584dfd0060baae1c","repo":"gitbutlerapp/gitbutler","slug":"projecthandle-payload-must-decode-to-an-absolute-f","errorCode":null,"errorMessage":"ProjectHandle payload must decode to an absolute filesystem path, got '{}'","messagePattern":"ProjectHandle payload must decode to an absolute filesystem path, got '(.+?)'","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-project-handle/src/project_handle.rs","lineNumber":112,"sourceCode":"}\n\nimpl TryFrom<ProjectHandle> for PathBuf {\n    type Error = anyhow::Error;\n\n    fn try_from(value: ProjectHandle) -> Result<Self, Self::Error> {\n        value.into_path()\n    }\n}\n\nfn encoded_str_to_path(encoded: &str) -> anyhow::Result<PathBuf> {\n    let bytes = decode(encoded)?;\n    let path = gix::path::try_from_byte_slice(&bytes)\n        .map_err(anyhow::Error::from)\n        .with_context(|| {\n            format!(\"Encoded ProjectHandle payload is not a valid filesystem path: '{encoded}'\")\n        })?;\n    if !path.is_absolute() {\n        bail!(\n            \"ProjectHandle payload must decode to an absolute filesystem path, got '{}'\",\n            path.display()\n        );\n    }\n    Ok(path.to_owned())\n}\n\nfn path_to_string(path: &Path) -> Result<String, anyhow::Error> {\n    let bytes = gix::path::os_str_into_bstr(path.as_os_str())?;\n    Ok(encode(bytes))\n}\n\nfn encode(bytes: &[u8]) -> String {\n    URL_SAFE_NO_PAD.encode(bytes)\n}\n\nfn decode(encoded: &str) -> anyhow::Result<Vec<u8>> {\n    URL_SAFE_NO_PAD","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-project-handle/src/project_handle.rs#L94-L130","documentation":"`ProjectHandle` encodes a repository location into a portable string payload. On decode, `encoded_str_to_path` base-decodes the payload and converts the bytes to a path via gix; a payload that decodes but is not an absolute path is rejected, because the handle must identify a workspace by absolute filesystem location.","triggerScenarios":"Creating or decoding a ProjectHandle whose payload encodes a relative path such as '.', '..', or 'my-repo' — typically a path captured with a different working directory or stored relative in config.","commonSituations":"Paths persisted from another machine or shell with a different cwd; config entries storing relative paths; tests and scripts using relative fixture paths.","solutions":["Absolutize the path before encoding it into the handle (std::fs::canonicalize or std::path::absolute)","Reject relative paths at the source (config loader, CLI parser) with a clear message","Re-derive the handle from the repository's absolute workdir"],"exampleFix":"// before\nlet handle = ProjectHandle::from_path(Path::new(\"my-repo\"))?; // relative -> bails on decode\n\n// after\nlet abs = std::path::absolute(Path::new(\"my-repo\"))?;\nlet handle = ProjectHandle::from_path(abs)?;","handlingStrategy":"validation","validationCode":"// Rust: only build handles from absolute paths\nlet abs = if path.is_absolute() {\n    path.to_path_buf()\n} else {\n    std::path::absolute(path)?\n};\nlet handle = ProjectHandle::from_path(abs)?;","typeGuard":"// Rust\nfn is_valid_project_path(p: &std::path::Path) -> bool {\n    p.is_absolute()\n}","tryCatchPattern":"Catch the decode error, re-derive the path from the repository's absolute workdir (e.g. via canonicalize), rebuild the handle, and retry once.","preventionTips":["Always store absolute paths in config and databases; canonicalize user input at the boundary","Reject relative paths in CLI/config parsers with an explicit message before they reach the handle"],"tags":["project-handle","path-validation","absolute-path","encoding"],"backgroundTag":"relative-path-rejected","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}