"Permission denied" renaming a folder on Windows is usually an open handle, not a permission
You try to rename a directory and get blocked:
# Git Bash
$ mv my-ai-team my-ai-team.old.1
mv: cannot move 'my-ai-team' to 'my-ai-team.old.1': Permission denied
# PowerShell, even elevated
PS> Move-Item .\my-ai-team .\my-ai-team.old.1
Move-Item: You do not have sufficient access rights to perform this operation
or the item is hidden, system, or read only.
The instinct is to reach for chown/takeown/icacls. On Windows that's usually the wrong tree to bark up. When even an administrator shell is denied, the cause is almost never the ACL — it's a runtime lock: some process has a file inside the directory open, or has the directory itself as its current working directory. Windows surfaces that lock as "Access denied," which reads like a permissions problem but isn't.
Rule out permissions in two commands
First confirm it really isn't attributes or ACLs, so you stop chasing them:
# Attributes: is it a reparse point / read-only / system / hidden?
Get-Item .\my-ai-team -Force | Format-List Name, Attributes, Target, LinkType
# ACLs: do you actually have rights?
icacls .\my-ai-team
In the case that prompted this note:
Attributes : Directory # not ReparsePoint, not ReadOnly, not System
...
XEMT\David.Wei:(I)(OI)(CI)(F) # inherited Full control — rights are fine
Clean attributes + your account with (F) + still denied = it's a lock, not a permission. Move on.
Note from Git Bash:
icaclsandattribneed real Windows paths, not/c/Users/.... Convert withcygpath:icacls "$(cygpath -w ~/.local/share/my-ai-team)"Also,
handle.exeis a separate Sysinternals download — don't assume it's installed.
Find the process holding the directory
Two built-in ways, no install needed.
Resource Monitor — resmon → CPU tab → Associated Handles search box. Search a distinctive slice of the full path, not the bare folder name (you may have several folders by the same name):
share\my-ai-team
This lists every process with a handle under that path. In the real case it surfaced two bash.exe running ...\bin\mux-session-exit-hook — orphaned session-exit hooks from an agent's terminal multiplexer that never finished exiting.
Command line match — catches processes cwd'd into or launched against the path, which the handle search can miss (handles sometimes appear as \Device\HarddiskVolumeN\... instead of a drive letter):
Get-CimInstance Win32_Process |
Where-Object { $_.CommandLine -like '*my-ai-team*' } |
Select-Object ProcessId, Name, CommandLine | Format-List
Kill precisely, then rename
Target by the script/command that's stuck, not just by bash.exe, so you don't take down an unrelated Git Bash:
Get-CimInstance Win32_Process |
Where-Object { $_.Name -eq 'bash.exe' -and $_.CommandLine -like '*mux-session-exit-hook*' } |
ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
Then the mv / Move-Item succeeds immediately. If you can't pin down the process, a reboot guarantees the handle is released.
Check state before settling on a name
If a previous rename attempt half-succeeded (folder renamed but old handles lingering), the handle path may already show the new name. Before deciding the final name, just look:
Get-ChildItem -Force | Where-Object Name -like 'my-ai-team*'
That tells you whether you still need .old.1 or whether it already exists and you want .old.2.
Key Points
- On Windows, "Permission denied" / "access rights" on a rename — especially when an elevated shell also fails — usually means an open handle or a process cwd'd into the directory, not an ACL problem.
- Rule out permissions fast:
Get-Item -Force(attributes: ReparsePoint/ReadOnly/System) andicacls(your account has(F)?). Clean + still denied = it's a lock. - Find the locker with
resmon→ CPU → Associated Handles (search a distinctive path slice), orGet-CimInstance Win32_Processfiltered onCommandLine. - Kill by the specific stuck command, not the bare interpreter name, to avoid collateral; a reboot is the guaranteed fallback.
- From Git Bash, feed
icacls/attribreal Windows paths viacygpath -w, and don't assume Sysinternalshandle.exeis present.