From f4c4ec6211b21422f40b3d675a4d997e46292f2e Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 6 May 2026 23:50:52 -0400 Subject: [PATCH] docs(build-hooks): correct staging-dir cleanup comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous comment claimed "rmdir-on-non-empty is a no-op" — that is factually wrong. fs.rmdirSync throws ENOTEMPTY on non-empty directories. The actual race-safety mechanism is: 1. fs.readdirSync(STAGE_DIR) -> leftovers 2. fs.rmdirSync(STAGE_DIR) only when leftovers.length === 0 3. Outer try/catch swallows TOCTOU ENOTEMPTY (peer added a file between readdir and rmdir) and ENOENT (peer already cleaned up). Comment now references the leftovers variable and both fs calls so a future reader can map narrative to code without reverse-engineering it. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/build-hooks.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/scripts/build-hooks.js b/scripts/build-hooks.js index 2b1b5451a..e0563c3e5 100644 --- a/scripts/build-hooks.js +++ b/scripts/build-hooks.js @@ -165,14 +165,21 @@ function build() { } // Best-effort cleanup of the staging dir. If concurrent builders are still - // running, leftover files belong to them and will be cleaned up on their - // own renames; rmdir-on-non-empty is a no-op so this is race-safe. + // running, their staged files will be left in STAGE_DIR and cleaned up by + // whichever builder calls fs.rmdirSync last. fs.rmdirSync throws ENOTEMPTY + // on a non-empty directory (it is NOT a silent no-op), so we first read + // the directory via fs.readdirSync(STAGE_DIR) -> leftovers and only call + // fs.rmdirSync(STAGE_DIR) when leftovers.length === 0. A TOCTOU window + // remains: another builder can drop a staged file between the readdirSync + // and the rmdirSync, in which case rmdirSync still throws ENOTEMPTY — the + // outer try/catch swallows that, plus ENOENT if the dir was already + // removed by a peer. Either way, build proceeds; cleanup is best-effort. try { const leftovers = fs.readdirSync(STAGE_DIR); if (leftovers.length === 0) { fs.rmdirSync(STAGE_DIR); } - } catch (e) { /* tolerate races / missing dir */ } + } catch (e) { /* tolerate TOCTOU ENOTEMPTY or ENOENT from peer cleanup */ } if (hasErrors) { console.error('\n\x1b[31mBuild failed: fix syntax errors above before publishing.\x1b[0m');