Skip to content

fix: tolerate Bun's spurious EEXIST from recursive mkdir on Windows ReadOnly dirs#2329

Open
Vaughan-g-aus wants to merge 1 commit into
garrytan:mainfrom
Vaughan-g-aus:fix/windows-readonly-mkdir-eexist
Open

fix: tolerate Bun's spurious EEXIST from recursive mkdir on Windows ReadOnly dirs#2329
Vaughan-g-aus wants to merge 1 commit into
garrytan:mainfrom
Vaughan-g-aus:fix/windows-readonly-mkdir-eexist

Conversation

@Vaughan-g-aus

Copy link
Copy Markdown

Problem

On Windows, Bun (reproduced on 1.3.11) throws EEXIST from
fs.mkdirSync(dir, { recursive: true }) when the directory already exists and
carries FILE_ATTRIBUTE_READONLY — an attribute Explorer sets as a "customized
folder" marker that does not actually prevent writes. Node treats the identical
call as a no-op success.

On affected machines ./setup cannot complete:

  • gen-skill-docs.ts fails Claude generation at the section-template mkdir
    (ship/sections is a committed directory):
    WARNING: claude generation failed: EEXIST: file already exists, mkdir '...\ship\sections'
  • Every external host fails at the per-host output mkdir
    (e.g. .agents/skills/gstack from a prior run), so --host all reports
    10 host(s) failed, exits 1, and set -e aborts setup.

This is the same Bun EEXIST class already tracked in #2048 (browse/mkdirSecure).
Bun refs: oven-sh/bun#16466 fixed the plain-existing-dir variant; the ReadOnly
variant still reproduces on 1.3.11.

Fix

Add scripts/fs-utils.ts with mkdirpSync() — a drop-in for recursive
mkdirSync that swallows EEXIST only after statSync confirms the path is an
existing directory
(a collision with a regular file still throws, and there is
no exists-check race). Use it at every mkdir site in gen-skill-docs.ts (5 sites)
and gen-llms-txt.ts (1 site).

Verification (Windows 11, Bun 1.3.11)

Case before after
--host claude with ReadOnly on ship/sections claude generation failed: EEXIST GENERATED: SKILL.md
--host codex with ReadOnly on .agents/skills/gstack codex generation failed: EEXIST all files generated
mkdir where a regular FILE occupies the path throws still throws (guard verified)

The branch is based on 7c9df1c (v1.60.1.0); the touched files are unchanged on
current main, so it merges cleanly there too.

…eadOnly dirs

On Windows, Bun (reproduced on 1.3.11) throws EEXIST from
fs.mkdirSync(dir, { recursive: true }) when the directory already exists
AND carries FILE_ATTRIBUTE_READONLY - an attribute Explorer sets as a
"customized folder" marker that does not actually prevent writes. Node
treats the identical call as a no-op success.

gen-skill-docs.ts hits this in two places on affected machines: the
section-template mkdir (ship/sections is a committed dir) fails Claude
generation, and the per-host output mkdir fails every external host, so
`gen:skill-docs --host all` exits 1 and ./setup aborts.

Add mkdirpSync(): swallow EEXIST only after stat confirms an existing
directory (a collision with a regular file still throws), and use it at
every mkdir site in gen-skill-docs.ts and gen-llms-txt.ts.

Verified on Windows 11 / Bun 1.3.11: with ReadOnly set on ship/sections
and .agents/skills/gstack, --host claude and --host codex both fail
before this change and succeed after; a file-collision still throws.

Related: garrytan#2048 (same Bun EEXIST class in browse/mkdirSecure).
@trunk-io

trunk-io Bot commented Jul 23, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@Vaughan-g-aus

Copy link
Copy Markdown
Author

/trunk merge

@trunk-io

trunk-io Bot commented Jul 23, 2026

Copy link
Copy Markdown

An error occurred while submitting your PR to the queue: Only users that are a part of this repo's Trunk organization or have write permissions to the repo can submit a PR to the queue

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