[BUG] bmad setup fails on Windows when _bmad/ already exists (WinError 183 in replace_dir)
Description
On Windows, bmad setup fails with FileExistsError: [WinError 183] whenever {project-root}/_bmad already exists. The first setup run on a clean project succeeds (that path does a plain src.rename(dest)), but every subsequent run aborts.
The cause is in skills/bmad/scripts/setup.py → replace_dir() (main, line 1178):
backup = Path(tempfile.mkdtemp(prefix="_bmad.old-", dir=dest.parent))
try:
dest.rename(backup)
tempfile.mkdtemp() creates the backup directory, and then dest.rename(backup) is asked to rename onto it. On POSIX, rename() onto an existing empty directory succeeds, so this works on macOS/Linux. Windows MoveFileEx without MOVEFILE_REPLACE_EXISTING refuses any existing target — and it never replaces an existing directory — so the rename always raises.
replace_dir() is used by both setup and doctor (materialize_bmad / the doctor staging path), so bmad doctor is affected the same way on Windows.
Steps to reproduce
- On Windows, in a project directory, run
bmad setup(via thebmad-toolboxplugin skill) — this succeeds and creates_bmad/. - Run
bmad setupagain in the same project. - The script exits non-zero with a traceback.
Equivalent direct invocation:
uv run --no-cache "{skill-root}/scripts/setup.py" --project-root "{project-root}" --skill "{skill-root}"
Expected behavior
The second run re-materializes _bmad/, preserving config.toml, custom/, and *.user.toml, and exits 0 — as it does on macOS/Linux.
Actual behavior
The run aborts. _bmad/ is left intact and the staging directory is cleaned up, so nothing is corrupted, but setup can never be re-run (and bmad doctor cannot repair) on Windows.
Relevant log output
Traceback (most recent call last):
File "...\skills\bmad\scripts\setup.py", line 1628, in <module>
raise SystemExit(main())
File "...\skills\bmad\scripts\setup.py", line 174, in main
setup(
File "...\skills\bmad\scripts\setup.py", line 224, in setup
materialize_bmad(
File "...\skills\bmad\scripts\setup.py", line 1381, in materialize_bmad
replace_dir(staging, bmad)
File "...\skills\bmad\scripts\setup.py", line 1395, in replace_dir
dest.rename(backup)
File "...\Python313\Lib\pathlib\_local.py", line 767, in rename
os.rename(self, target)
FileExistsError: [WinError 183] Cannot create a file when that file already exists:
'D:\<project>\_bmad' -> 'D:\<project>\_bmad.old-_n_hj7x0'
(Line numbers above are from the 6.13.0-next plugin copy; the same function is at line 1174 on main.)
Suggested fix
Reserve the unique name, then release it before the rename:
backup = Path(tempfile.mkdtemp(prefix="_bmad.old-", dir=dest.parent))
backup.rmdir()
try:
dest.rename(backup)
This keeps mkdtemp's uniqueness guarantee and leaves the existing rollback/cleanup logic untouched. I applied exactly this locally and bmad setup then re-ran cleanly and idempotently (3 consecutive runs, exit 0, config.toml and custom/ preserved, no _bmad.old-* left behind).
An alternative, if the tiny TOCTOU window matters, is generating a candidate name without creating it (e.g. dest.parent / f"_bmad.old-{uuid4().hex[:8]}" in a retry loop).
Environment
- Module: Not sure / Other — shared
bmadskill setup script (skills/bmad/scripts/setup.py) - BMad version:
bmad-toolboxplugin6.13.0-next - IDE: Claude Code
- OS: Windows 11 (26200), Python 3.13 via
uv0.12.12
Source: bmad-code-org/BMAD-METHOD