From 4203341227afdbd74a88844eb62dad0ed652d4df Mon Sep 17 00:00:00 2001 From: Andy <119136210+AndyMik90@users.noreply.github.com> Date: Wed, 7 Jan 2026 14:12:55 +0100 Subject: [PATCH] fix(permissions): grant worktree access to original project directories (#385) (#776) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(permissions): grant worktree access to original project directories (#385) When running agents in a worktree, the filesystem permissions now include access to the original project's .auto-claude/ and .worktrees/ directories. This fixes permission errors like: "Claude requested permissions to write to .worktrees/XXX/.auto-claude/specs/XXX/ implementation_plan.json, but you haven't granted it yet." The fix: - Detects when project_dir is inside a worktree (both new and legacy locations) - Extracts the original project directory path - Adds Read/Write/Edit/Glob/Grep permissions for: - Original project's .auto-claude/ directory - Original project's .worktrees/ directory (legacy support) - Cross-platform compatible (Unix and Windows path handling) Fixes #385 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix: address PR review feedback for worktree permissions - Use rsplit instead of split for nested path handling (Auto Claude) - Add leading slash to new worktree marker for consistency (Auto Claude) - Remove redundant Windows-specific markers since paths are normalized (Gemini) - Consolidate permission logic with loops to reduce duplication (Gemini) - Fix log message to reflect both .auto-claude/ and .worktrees/ access 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix: add PR review worktree marker for permission grants Add missing '/.auto-claude/github/pr/worktrees/' marker to ensure PR review agents get proper permissions to access original project directories when running in isolated worktrees. Addresses Auto Claude PR Review finding NEW-003. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * chore: add config.json to gitignore Prevent worktree metadata files from being accidentally committed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 Co-authored-by: Alex <63423455+AlexMadera@users.noreply.github.com> --- .gitignore | 1 + apps/backend/core/client.py | 47 +++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/.gitignore b/.gitignore index f7e4711e..28b23f32 100644 --- a/.gitignore +++ b/.gitignore @@ -168,3 +168,4 @@ _bmad-output/ /docs OPUS_ANALYSIS_AND_IDEAS.md /.github/agents +/config.json diff --git a/apps/backend/core/client.py b/apps/backend/core/client.py index c50978ee..761cf2cf 100644 --- a/apps/backend/core/client.py +++ b/apps/backend/core/client.py @@ -545,6 +545,48 @@ def create_client( # cases where Claude uses absolute paths for file operations project_path_str = str(project_dir.resolve()) spec_path_str = str(spec_dir.resolve()) + + # Detect if we're running in a worktree and get the original project directory + # Worktrees are located in either: + # - .auto-claude/worktrees/tasks/{spec-name}/ (new location) + # - .worktrees/{spec-name}/ (legacy location) + # When running in a worktree, we need to allow access to both the worktree + # and the original project's .auto-claude/ directory for spec files + original_project_permissions = [] + resolved_project_path = project_dir.resolve() + + # Check for worktree paths and extract original project directory + # This handles spec worktrees, PR review worktrees, and legacy worktrees + # Note: Windows paths are normalized to forward slashes before comparison + worktree_markers = [ + "/.auto-claude/worktrees/tasks/", # Spec/task worktrees + "/.auto-claude/github/pr/worktrees/", # PR review worktrees + "/.worktrees/", # Legacy worktree location + ] + project_path_posix = str(resolved_project_path).replace("\\", "/") + + for marker in worktree_markers: + if marker in project_path_posix: + # Extract the original project directory (parent of worktree location) + # Use rsplit to get the rightmost occurrence (handles nested projects) + original_project_str = project_path_posix.rsplit(marker, 1)[0] + original_project_dir = Path(original_project_str) + + # Grant permissions for relevant directories in the original project + permission_ops = ["Read", "Write", "Edit", "Glob", "Grep"] + dirs_to_permit = [ + original_project_dir / ".auto-claude", + original_project_dir / ".worktrees", # Legacy support + ] + + for dir_path in dirs_to_permit: + if dir_path.exists(): + path_str = str(dir_path.resolve()) + original_project_permissions.extend( + [f"{op}({path_str}/**)" for op in permission_ops] + ) + break + security_settings = { "sandbox": {"enabled": True, "autoAllowBashIfSandboxed": True}, "permissions": { @@ -567,6 +609,9 @@ def create_client( f"Read({spec_path_str}/**)", f"Write({spec_path_str}/**)", f"Edit({spec_path_str}/**)", + # Allow original project's .auto-claude/ and .worktrees/ directories + # when running in a worktree (fixes issue #385 - permission errors) + *original_project_permissions, # Bash permission granted here, but actual commands are validated # by the bash_security_hook (see security.py for allowed commands) "Bash(*)", @@ -603,6 +648,8 @@ def create_client( print(f"Security settings: {settings_file}") print(" - Sandbox enabled (OS-level bash isolation)") print(f" - Filesystem restricted to: {project_dir.resolve()}") + if original_project_permissions: + print(" - Worktree permissions: granted for original project directories") print(" - Bash commands restricted to allowlist") if max_thinking_tokens: print(f" - Extended thinking: {max_thinking_tokens:,} tokens")