Skip to content

fix(worktree): clear genuine ACL denials on orphaned worktree cleanup - #322

Merged
getappz merged 4 commits into
masterfrom
task/267
Jul 25, 2026
Merged

fix(worktree): clear genuine ACL denials on orphaned worktree cleanup#322
getappz merged 4 commits into
masterfrom
task/267

Conversation

@getappz

@getappz getappz commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes agentflare item #267, under epic #337.

Test plan

  • cargo build --workspace --all-features — clean
  • cargo fmt --check -p flare-git-core — clean
  • cargo test -p flare-git-core --all-features — 106 passed, 0 failed
  • cargo clippy -p flare-git-core --all-features -- -D warnings — clean
  • New test remove_worktree_dir_clears_a_genuine_acl_denial (Windows-only) actually sets a real icacls /deny on a file, confirms the existing retry+rmdir fallbacks alone can't clear it, then confirms the new icacls /grant step does

Summary by CodeRabbit

  • Bug Fixes
    • Improved Windows worktree cleanup when removal fails due to access-denied permissions.
    • Added an ACL recovery fallback that resets the affected directory’s permissions and retries removal, reducing leftover temporary worktrees.
    • Added a Windows-only unit test that simulates genuine ACL denial to confirm cleanup now succeeds and the directory is fully removed.

remove_worktree_dir's existing retry loop and cmd /c rmdir fallback
both only clear a transient in-use lock (e.g. item #302's rust-analyzer
case). Neither touches a genuine ACL denial, which is item #267's
actual failure mode: cargo's own target/*/.fingerprint/* files can end
up ACL-restricted, not merely open, and no amount of retrying clears
that.

Adds one more fallback before giving up: icacls /grant <user>:F /T
resets ownership access recursively, then one final remove_dir_all
attempt. No-op (and thus never destructive) when the real problem was
actually an in-use lock the earlier retries already cleared.

Considered switching to git-parsec's shared_cache symlink strategy
instead (share target/ across worktrees so there's nothing per-worktree
to get ACL-locked in the first place) -- rejected: this codebase
already hit and fixed the exact correctness bug that would reintroduce
(item #139, cargo #12516/#14053/#7740 -- a shared CARGO_TARGET_DIR's
fingerprint hash omits the worktree path, so two worktrees of different
branches silently reuse each other's stale local crate artifacts).
isolate_worktree_target_dir's per-worktree isolation is deliberate, not
an oversight; sccache (item #133) already covers the safe part of
cross-worktree cache sharing (registry deps, hash-keyed).

Agentflare-Agent: claude-code_2-1-219_agent
Agentflare-Branch: task/267
Agentflare-Item: 267
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c5c833a5-1bc1-4d49-a26b-8cbcc454191f

📥 Commits

Reviewing files that changed from the base of the PR and between 0babb71 and 28f5b3e.

📒 Files selected for processing (1)
  • crates/flare-git-core/src/worktree.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/flare-git-core/src/worktree.rs

📝 Walkthrough

Walkthrough

Windows worktree removal now clears genuine ACL denials with icacls before retrying deletion. A Windows-only test verifies that an ACL-denied worktree directory is removed successfully.

Changes

Windows ACL Cleanup

Layer / File(s) Summary
ACL fallback and validation
crates/flare-git-core/src/worktree.rs
The Windows fallback grants full control with icacls and retries remove_dir_all; a Windows-only test verifies cleanup after denying file permissions.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main Windows ACL-fallback change in worktree cleanup.
Description check ✅ Passed The description includes a detailed Summary and Test plan and is mostly complete, though it omits the Notes for reviewers section.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task/267

Comment @coderabbitai help to get the list of available commands.

Agentflare-Agent: claude-code_2-1-219_agent
Agentflare-Branch: task/267
Agentflare-Item: 267
@getappz
getappz enabled auto-merge (squash) July 25, 2026 03:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/flare-git-core/src/worktree.rs`:
- Around line 622-636: Update the Windows ACL fallback in the retry block around
std::process::Command::new("icacls") to clear or rebuild deny ACEs before
granting the current user full control, then retry remove_dir_all. In the
related test at crates/flare-git-core/src/worktree.rs:709-724, deny deletion of
the directory tree or the parent’s delete-child permission rather than only
locked.txt’s Delete permission, so the fallback is reliably exercised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1984da5d-3594-416c-a3ec-0a8d72134056

📥 Commits

Reviewing files that changed from the base of the PR and between f696e27 and 0babb71.

📒 Files selected for processing (1)
  • crates/flare-git-core/src/worktree.rs

Comment on lines +622 to +636
if let Ok(user) = std::env::var("USERNAME") {
let icacls_args: Vec<String> = vec![
path.to_string_lossy().to_string(),
"/grant".to_string(),
format!("{user}:F"),
"/T".to_string(),
"/C".to_string(),
"/Q".to_string(),
];
let _ = std::process::Command::new("icacls")
.args(&icacls_args)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status();
if std::fs::remove_dir_all(path).is_ok() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== worktree.rs around cited lines ==\n'
sed -n '600,660p' crates/flare-git-core/src/worktree.rs
printf '\n== worktree.rs test region ==\n'
sed -n '690,740p' crates/flare-git-core/src/worktree.rs
printf '\n== search for remove_worktree_dir and ACL helpers ==\n'
rg -n "remove_worktree_dir|icacls|USERNAME|deny|grant|delete" crates/flare-git-core/src/worktree.rs

Repository: getappz/agentflare

Length of output: 6659


🌐 Web query:

icacls /grant explicit deny ace does not remove deny delete child permission parent delete-child documentation

💡 Result:

The behavior you are observing is a documented limitation of the icacls command-line tool, compounded by how NTFS handles specific permission masks and ACE removal. 1. Lack of granular removal: There is no command in icacls to remove a single, specific ACE (such as Deny Delete Child) [1][2]. The /remove command only operates at the SID (User/Group) level [1][3]. Using /remove:d [User] will remove all Deny ACEs for that user, not just the one you intended to target [1][2]. 2. Precedence and Side Effects: When you use /deny, icacls may include additional implicit permissions like Synchronize (S) in the ACE mask [4][5]. Consequently, simply granting a permission does not automatically "remove" the corresponding Deny ACE; the Deny ACE remains in the Access Control List (ACL) [1]. Because Windows processes the DACL in a specific order—typically evaluating explicit Deny ACEs before explicit Grant ACEs—the Deny ACE will continue to take precedence and block the action, even if you subsequently attempt to grant that same permission [6][7]. 3. Recommended Workarounds: - To effectively manage specific permissions without unintended side effects, avoid using /deny if possible. Instead, structure your permissions using Allow ACEs only [6][7]. - If you must remove a specific Deny ACE created by icacls, you may need to use third-party tools (such as SetACL) that provide granular control over ACE manipulation [5]. - If you must continue using native tools, you may have to remove all permissions for the user using /remove:d and then re-apply the desired Grant ACEs to reconstruct the ACL correctly [1][2]. For further technical context, Microsoft has documented that the icacls program manages permission masks (like Delete vs. Delete Child) in ways that can differ from the GUI, sometimes including Synchronize (S) automatically, which can lead to unexpected access blocks (e.g., inability to traverse a folder) [4][5].

Citations:


🌐 Web query:

Microsoft NTFS delete child explicit deny delete permission child file parent directory access check

💡 Result:

In the Microsoft NTFS file system, the ability to delete a file or directory is governed by a dual-path authorization mechanism [1][2][3]. A user can successfully delete a child object if they satisfy either of the following conditions: 1. They have the DELETE permission explicitly granted on the child object itself [1][2][3]. 2. They have the DELETE_CHILD (also known as "Delete subfolders and files") permission granted on the parent directory [4][5][3]. If either of these conditions is met, the file system will typically allow the delete operation [1][2][3]. Consequently, if a user has DELETE_CHILD access on a parent directory, this permission effectively overrides a specific "Deny Delete" ACE (Access Control Entry) applied to the child file itself [6][2]. Because the system verifies security descriptors for both the parent and the object to determine if the deletion is authorized, an explicit "Deny" on the object does not necessarily block the operation if the parent's "Allow" (DELETE_CHILD) remains in effect [6]. To effectively prevent a user from deleting a file, you must ensure that both paths are restricted [1][2]. This requires: - Denying the DELETE permission on the child file itself [1][2]. - Denying the DELETE_CHILD permission on the parent directory [1][7]. If only one of these paths is restricted, the other will continue to provide the necessary authorization for the deletion to occur [1][2]. Documentation and technical analysis confirm that this behavior is by design, as the "Delete" operation is fundamentally treated as an action performed on the parent directory container, not solely on the child object itself [8][7].

Citations:


🌐 Web query:

site:learn.microsoft.com delete child permission delete file even if file has deny delete Windows ACL

💡 Result:

In Windows, the ability to delete a file is governed by two distinct sets of permissions: those assigned to the file itself (Delete permission) and those assigned to the parent directory (Delete Child permission) [1][2][3]. Because these are independent mechanisms, a user can delete a file if they possess either: 1. Delete permission on the file itself [1][3] 2. Delete Child permission on the parent directory [1][4][3] If a user is explicitly denied "Delete" access on a file, they can still successfully delete that file if they have "Delete Child" permission on the parent directory [4][3]. This occurs because the system evaluates these permissions independently; the "Delete Child" right on the directory grants the user authority over the directory's contents, which bypasses the restrictive ACL placed directly on the file [4][5][3]. To effectively prevent a user from deleting a file, you must ensure that they are denied the necessary permissions at both levels [4][3]: - File level: Explicitly deny "Delete" access [3]. - Directory level: Explicitly deny "Delete Child" (or "Delete subfolders and files") access on the parent directory [4][6][3]. Because "Delete Child" is a powerful privilege that can override file-level protections, it is standard security practice to carefully restrict who holds this permission on directories [4]. Furthermore, because the Windows security system evaluates access based on the first applicable "Deny" or "Allow" rule encountered (based on the Discretionary Access Control List (DACL) order), it is critical that "Deny" entries are properly configured and prioritized [7][8][9]. Note also that users with administrative privileges or those who hold the "Take Ownership" right can modify these permissions to regain access, regardless of existing Deny rules [3].

Citations:


icacls /grant doesn’t clear a deny ACE.
The retry can still fail on a real ACL denial because /grant leaves the explicit deny in place; clear the deny ACE first or rebuild the ACL before retrying.

The test also only denies Delete on locked.txt, which can still allow tree deletion via the parent directory’s delete-child right, so it doesn’t reliably exercise the fallback.

📍 Affects 1 file
  • crates/flare-git-core/src/worktree.rs#L622-L636 (this comment)
  • crates/flare-git-core/src/worktree.rs#L709-L724
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/flare-git-core/src/worktree.rs` around lines 622 - 636, Update the
Windows ACL fallback in the retry block around
std::process::Command::new("icacls") to clear or rebuild deny ACEs before
granting the current user full control, then retry remove_dir_all. In the
related test at crates/flare-git-core/src/worktree.rs:709-724, deny deletion of
the directory tree or the parent’s delete-child permission rather than only
locked.txt’s Delete permission, so the fallback is reliably exercised.

getappz added 2 commits July 25, 2026 08:39
Agentflare-Agent: claude-code_2-1-219_agent
Agentflare-Branch: task/267
Agentflare-Item: 267
Agentflare-Agent: claude-code_2-1-219_agent
Agentflare-Branch: task/267
Agentflare-Item: 267
@getappz
getappz merged commit cd0b68a into master Jul 25, 2026
16 checks passed
@getappz
getappz deleted the task/267 branch July 25, 2026 03:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant