You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is AI slop, triggered by @rafaelcaricio. This was human-reviewed and I (the human) did run those commands to reproduce the issue.
I build a coding agent eval harness on top of Gondolin. The harness clones a project into the workspace, and then an agent edits and builds it inside the sandbox. To keep the workspace clean, I shadow any path that the project's .gitignore marks as ignored. I use ShadowProvider with a RealFSProvider backend and writeMode set to "tmpfs", so writes to ignored paths go to a tmpfs layer instead of the real workspace. As a result, build artifacts stay out of the workspace I collect after the run.
The problem
When a program creates a directory that is shadowed, but the parent directory is not shadowed, the mkdir call fails with ENOENT. The parent exists in the merged view the program sees, so the error is confusing.
Real trigger
Real builds fail at this step. The FFmpeg build calls mkdir -p doc/examples/pc-uninstalled:
mkdir -p doc/examples/pc-uninstalled at ffbuild/pkgconfig_generate.sh line 46 (source).
pc-uninstalled is listed in doc/examples/.gitignore line 16 (source).
The path is ignored, so my predicate shadows it. The mkdir fails and the build stops. I ran two different coding agents against this build, and each one spent about two hours stuck on this single step before I stopped them. Both agents correctly guessed the filesystem was at fault.
How to reproduce
This script reproduces the bug. It sets up the same provider shape I use, then creates a shadowed directory whose parent lives in the backend.
Run it in a folder with Gondolin installed:
mkdir gondolin-repro &&cd gondolin-repro
npm init -y >/dev/null
npm install @earendil-works/gondolin
cat > repro.mjs <<'JS'import { ShadowProvider, MemoryProvider } from "@earendil-works/gondolin";const backend = new MemoryProvider();// The parent exists in the backend, like a source tree after checkout.await backend.mkdir("/project/doc/examples", { recursive: true });const provider = new ShadowProvider(backend, { // Mirror my gitignore shadowing: pc-uninstalled is ignored. shouldShadow: ({ path }) => /\/pc-uninstalled(\/|$)/.test(path), writeMode: "tmpfs", tmpfs: new MemoryProvider(),});const target = "/project/doc/examples/pc-uninstalled";try { await provider.mkdir(target); console.log("OK: mkdir succeeded. The bug is not present on this version."); process.exit(0);} catch (err) { console.log("FAIL: mkdir threw " + (err.code || err.name) + ". The bug is present."); console.log(" " + err.message); process.exit(1);}JS
node repro.mjs
On Gondolin 0.12.0 this prints:
FAIL: mkdir threw ENOENT. The bug is present.
ENOENT: no such file or directory, mkdir '/project/doc/examples/pc-uninstalled'
Why it happens
In ShadowProvider.mkdir, when the path is shadowed, the code calls tmpfs.mkdir(p, options). The tmpfs is a separate MemoryProvider that starts empty, so it does not contain the parent directory. MemoryProvider.mkdirSync without recursive set calls _ensureParent with create set to false, and that throws ENOENT when the parent is missing.
File writes do not hit this. openSync calls _ensureParent with create set to true, so writing a shadowed file under a parent that is not shadowed works. Only mkdir passes create as false.
Fix
Pass recursive: true when the mkdir is shadowed, so tmpfs creates the parent chain.
This is safe because the parent is not shadowed. Anything that reads or lists the parent still reads from the backend, so the directories tmpfs creates as parents are never visible. Only the shadowed leaf shows up.
I patched this in my project with this change, and the reproduction above prints OK for me.
Note
This is AI slop, triggered by @rafaelcaricio. This was human-reviewed and I (the human) did run those commands to reproduce the issue.
I build a coding agent eval harness on top of Gondolin. The harness clones a project into the workspace, and then an agent edits and builds it inside the sandbox. To keep the workspace clean, I shadow any path that the project's
.gitignoremarks as ignored. I useShadowProviderwith aRealFSProviderbackend andwriteModeset to"tmpfs", so writes to ignored paths go to a tmpfs layer instead of the real workspace. As a result, build artifacts stay out of the workspace I collect after the run.The problem
When a program creates a directory that is shadowed, but the parent directory is not shadowed, the
mkdircall fails withENOENT. The parent exists in the merged view the program sees, so the error is confusing.Real trigger
Real builds fail at this step. The FFmpeg build calls
mkdir -p doc/examples/pc-uninstalled:mkdir -p doc/examples/pc-uninstalledatffbuild/pkgconfig_generate.shline 46 (source).pc-uninstalledis listed indoc/examples/.gitignoreline 16 (source).The path is ignored, so my predicate shadows it. The
mkdirfails and the build stops. I ran two different coding agents against this build, and each one spent about two hours stuck on this single step before I stopped them. Both agents correctly guessed the filesystem was at fault.How to reproduce
This script reproduces the bug. It sets up the same provider shape I use, then creates a shadowed directory whose parent lives in the backend.
Run it in a folder with Gondolin installed:
On Gondolin 0.12.0 this prints:
Why it happens
In
ShadowProvider.mkdir, when the path is shadowed, the code callstmpfs.mkdir(p, options). The tmpfs is a separateMemoryProviderthat starts empty, so it does not contain the parent directory.MemoryProvider.mkdirSyncwithoutrecursiveset calls_ensureParentwithcreateset to false, and that throwsENOENTwhen the parent is missing.File writes do not hit this.
openSynccalls_ensureParentwithcreateset to true, so writing a shadowed file under a parent that is not shadowed works. Onlymkdirpassescreateas false.Fix
Pass
recursive: truewhen themkdiris shadowed, so tmpfs creates the parent chain.The same change applies to
mkdirSync.This is safe because the parent is not shadowed. Anything that reads or lists the parent still reads from the backend, so the directories tmpfs creates as parents are never visible. Only the shadowed leaf shows up.
I patched this in my project with this change, and the reproduction above prints OK for me.
Tested on
@earendil-works/gondolin0.12.0.