fix(redact): tolerate EEXIST from recursive mkdir in install-prepush-hook on bun/Windows (#2635)

fs.mkdirSync(dir, { recursive: true }) is a no-op on an existing directory
in Node, but bun on Windows throws EEXIST - crashing hook install on any
repo whose .git/hooks already existed, leaving the repo unprotected.

Add lib/fs-utils.ts mkdirpSync: swallow EEXIST only when statSync confirms
the path is an existing directory; a regular file occupying the path, a
stat failure, or any other errno still rethrows. Use it in
installPrepushHook().

The regression test emulates the Windows bun fs semantics via a
bun --preload fixture, so the exact crash path runs (and fails on the old
code) on any platform, including CI Linux.

Absorbed from PR #2641 with authorship preserved.

Fixes #2635
This commit is contained in:
Lockyer
2026-08-22 01:57:28 +00:00
committed by Garry Tan
parent b15bb9d9b8
commit 790c42505a
4 changed files with 141 additions and 1 deletions
+4 -1
View File
@@ -37,6 +37,7 @@ import {
type ScanOptions,
type Finding,
} from "../lib/redact-engine";
import { mkdirpSync } from "../lib/fs-utils";
const MAX_STDIN_BYTES = 16 * 1024 * 1024; // hard ceiling before the engine cap
@@ -55,7 +56,9 @@ function hooksPath(): string {
function installPrepushHook(): void {
const dir = hooksPath();
fs.mkdirSync(dir, { recursive: true });
// mkdirpSync, not bare mkdirSync: bun on Windows throws EEXIST from a
// recursive mkdir when .git/hooks already exists (#2635).
mkdirpSync(dir);
const hookPath = path.join(dir, "pre-push");
const prepushBin = path.join(import.meta.dir, "gstack-redact-prepush");