gitbutlerapp/gitbutler · error
Path to read from at '{}' isn't in the worktree directory '{
Error message
Path to read from at '{}' isn't in the worktree directory '{}' What it means
Thrown by read_file_from_workspace when the requested path, after both it and the worktree root are canonicalized with gix::path::realpath, no longer lies inside the worktree. The double-check exists because the input may be absolute or travel through symlinks; realpath resolves symlinks, so a link inside the worktree pointing outside is caught. This is a sandbox guard preventing reads of arbitrary files through the workspace API.
Source
Thrown at crates/gitbutler-repo/src/commands.rs:308
Some(entry) => {
let blob = repo.find_blob(entry.id())?;
FileInfo::from_content(relative_path, &blob.data)
}
None => FileInfo::deleted(),
})
}
/// Note that `path` can be relative or absolute, and we must validate that it's in the worktree.
fn read_file_from_workspace(&self, path: &Path) -> Result<FileInfo> {
let workdir = self.workdir_or_fail()?;
let canonical_workdir = gix::path::realpath(&workdir)?;
let path = gix::path::realpath(canonical_workdir.join(path))?;
// Double-check that the path is still in the worktree - this might not be the case
// if it was aboslute to begin with, or leads through symlinks.
let relative_path = match path.strip_prefix(&canonical_workdir) {
Ok(relative_path) => relative_path.to_owned(),
Err(_) => {
bail!(
"Path to read from at '{}' isn't in the worktree directory '{}'",
path.display(),
canonical_workdir.display()
);
}
};
let out = match path.symlink_metadata() {
Ok(md) => {
if md.is_file() {
let content = std::fs::read(&path)?;
FileInfo::from_content(&relative_path, &content)
} else if md.is_symlink() {
let content = std::fs::read_link(&path)?;
FileInfo::utf8_text_or_binary(&relative_path, &gix::path::into_bstr(content))
} else if md.is_dir() {
// Directories on disk (notably git submodules, which appear
// as real directories in the worktree but are representedView on GitHub (pinned to caf1f223d3)
Solutions
- Pass paths relative to the worktree root and let the API join them onto the workdir
- Remove or relocate symlinks that resolve outside the worktree
- If you legitimately need a file outside the worktree, read it directly with std::fs instead of this API
Example fix
// before: read_file_from_workspace(Path::new("/etc/hosts"))
// after: read only files inside the worktree, by relative path
read_file_from_workspace(Path::new("src/main.rs"))?; Defensive patterns
Strategy: validation
Validate before calling
let workdir = gix::path::realpath(repo.workdir())?;
let candidate = gix::path::realpath(workdir.join(path))?;
if candidate.strip_prefix(&workdir).is_err() {
// would escape the worktree (symlink or absolute path) - reject before calling
} Prevention
- Treat workspace file APIs as sandboxed to the worktree
- Audit symlinks inside repos consumed by automation
- Canonicalize user-supplied paths before making policy decisions
When it happens
Trigger: Calling read_file_from_workspace(path) where path is absolute and outside the worktree, contains '..' components that escape after canonicalization, or traverses a symlink inside the worktree that resolves to a location outside it - strip_prefix(canonical_workdir) then fails.
Common situations: Monorepos where build tooling symlinks node_modules or cache directories outside the tree; user- or agent-supplied absolute paths passed unfiltered; worktrees checked out under symlinked directories (macOS /tmp -> /private/tmp style) making prefix comparison fail.
Related errors
- Refusing to read '{relative_path:?}' from commit {commit_id:
- Invalid response: ${JSON.stringify(result)}
- When using OpenAI in a bring your own key configuration, you
- Failed to communicate with LM Studio server: ${error instanc
- When using GitButler's API to summarize code, you must be lo
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/6d8443b1a3ea0b7b.
Report an issue: GitHub.