You run the script. The file is right there. You can ls it, tab-complete it, stare at it like it owes you money.
And WSL replies with: No such file or directory.
This error is WSL’s favorite magic trick: it makes a real file “disappear” by attacking the path at a different layer than you’re looking at.
The fix is rarely “create the file.” The fix is understanding which filesystem you’re on, which path syntax is in play, and which translator is silently rewriting your assumptions.
The actual problem: your path is valid… in the wrong universe
WSL isn’t “Linux on Windows” so much as “Linux next to Windows with a treaty.” That treaty includes path translation, mount layers,
filename semantics, and an interop bridge that tries to be helpful until it isn’t.
When WSL says “No such file or directory,” it might mean:
- The file exists, but the interpreter named in the shebang doesn’t (or can’t be found).
- The file exists, but you’re on
/mnt/cand permissions/metadata mapping makes it effectively non-executable. - The file exists, but the path contains Windows escaping issues (spaces, parentheses, backslashes) and your shell is lying to you.
- The file exists, but it’s CRLF, so the interpreter path is actually
/bin/bash\r, which indeed does not exist. - The file exists, but you’re mixing UNC paths, symlinks, or case behavior and landing somewhere else.
- The file exists, but you’re calling it from a Windows process that’s “helpfully” rewriting the arguments.
Your job is to identify which layer is generating the “missing” condition. Don’t guess. Diagnose.
Fast diagnosis playbook
This is the order I check things when an on-call page says “pipeline failing in WSL, no such file, file is present.”
It’s optimized for getting to root cause fast, not for teaching you Linux fundamentals.
First: is this a path problem or an interpreter problem?
- If it’s a script: check the shebang and line endings (
head -n1,file). - If it’s a binary: check architecture and loader (
file,ldd).
Second: where is the file located?
/home(ext4 inside WSL2 VHDX) behaves like Linux./mnt/c(DrvFs) behaves like “Linux talking to NTFS through a compatibility layer.”- Network shares add another compatibility layer. That’s where joy goes to retire.
Third: who is calling whom?
- Linux calling Linux is usually sane.
- Linux calling Windows EXEs via interop has quoting/path edge cases.
- Windows calling into WSL (
wsl.exe) can rewrite working directory and arguments.
Fourth: validate the exact bytes of the path
- Look for hidden characters: CR, non-breaking spaces, smart quotes, Unicode normalization.
- Confirm the directory entry exists and is reachable (
stat), not just listed.
Fifth: identify mount options and metadata behavior
- DrvFs mount options decide whether
chmod +xmeans anything. - Case sensitivity can be per-directory on NTFS; it can change behavior between machines.
Run the tasks in the next section in order until something looks wrong. It will. That’s the point.
WSL path model, explained like you run prod
WSL2 gives you a real Linux kernel—and then hands you a bunch of translators
In WSL2, your Linux distribution lives on an ext4 filesystem inside a virtual disk (VHDX). That’s the “real Linux” part.
Your Windows drives are made visible in Linux via DrvFs mounts, typically under /mnt/c, /mnt/d, etc.
Those two worlds differ in ways that matter for “file not found”:
- Path syntax: Linux uses
/, Windows uses\and drive letters. - Case behavior: Linux is case-sensitive; Windows historically is not (but can be configured per directory).
- Executable semantics: Linux uses mode bits and interpreters; Windows uses file extensions and PE headers.
- Symlinks: Linux symlinks are native; Windows symlinks are… a policy debate.
When WSL says it can’t find a file, it might mean it can’t find a directory entry—or it can’t execute it
Linux has a wonderfully blunt error taxonomy. Multiple distinct problems collapse into similar messages:
ENOENT: no such file or directory (also thrown when the interpreter or dynamic loader is missing).EACCES: permission denied (often what you wanted to see, but didn’t).ENOTDIR: a path component wasn’t a directory (can happen with odd symlink targets).
WSL adds a twist: translation layers can return ENOENT when the Linux side requests something the Windows side can’t represent.
The result is an error that’s technically correct and operationally annoying.
Interop is powerful, but it’s a quoting and working-directory minefield
WSL lets you run Windows executables from Linux and Linux commands from Windows. That’s great until you pass:
- Paths with spaces or parentheses
- Paths that look like options (leading
-) - UNC paths (
\\server\share) - Arguments containing backslashes that your shell “helpfully” interprets
In incident terms: interop increases your blast radius. It’s convenient in dev; it’s fragile in automation unless you’re disciplined.
Interesting facts and history (because the past is still breaking your build)
- Fact 1: WSL1 translated Linux syscalls into Windows syscalls. WSL2 switched to a real Linux kernel in a lightweight VM, changing filesystem performance tradeoffs drastically.
- Fact 2: The default Windows drive mounts live under
/mntbecause early WSL aimed to resemble a typical Linux mount layout without colliding with distro directories. - Fact 3: NTFS historically behaved case-insensitively but case-preservingly; modern Windows can enable per-directory case sensitivity, which makes cross-tooling behavior… spicy.
- Fact 4: The classic CRLF problem predates WSL by decades; it’s a terminal and tooling artifact from DOS/Windows text conventions meeting Unix text conventions.
- Fact 5: Linux reports ENOENT not only when a target file is missing, but also when the ELF interpreter or dynamic loader referenced by a binary can’t be located.
- Fact 6: Windows paths can legally contain characters that are syntactically meaningful to shells (spaces, parentheses, ampersands). In a shell pipeline, that’s not “just a path,” that’s an incident.
- Fact 7: DrvFs permission mapping used to be a constant source of confusion; mount options like
metadataexist specifically to preserve Linux mode bits on Windows filesystems. - Fact 8: Git’s line-ending settings (autocrlf) are one of the most common reasons a script becomes “unexecutable” in Linux while still looking fine in Windows editors.
- Fact 9:
wsl.exechanged behavior across Windows releases regarding default working directory and path translation flags; scripts that assume one version’s behavior can fail on another.
Practical tasks: commands, outputs, and what they mean
Below are real diagnostics I use. Each task includes (1) the command, (2) what you might see, and (3) the decision you make.
Don’t run them all blindly in a production pipeline—run them in the failing environment with the failing inputs.
Task 1: Confirm you’re in WSL1 or WSL2 (it changes filesystem behavior)
cr0x@server:~$ uname -a
Linux server 5.15.146.1-microsoft-standard-WSL2 #1 SMP Fri Jan 12 18:22:05 UTC 2024 x86_64 GNU/Linux
Meaning: microsoft-standard-WSL2 indicates WSL2. If you see WSL1, path translation and filesystem semantics differ.
Decision: If it’s WSL1, expect more syscall-translation oddities; if it’s WSL2, focus heavily on DrvFs mounts and cross-filesystem performance/metadata.
Task 2: Identify where the file actually lives (Linux ext4 vs Windows drive)
cr0x@server:~$ df -T /home /mnt/c | sed -n '1,3p'
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/sdc ext4 263174212 93481244 156278972 38% /
C:\ 9p 976762876 512331244 464431632 53% /mnt/c
Meaning: ext4 is the WSL2 VHDX-backed filesystem; 9p here represents the Windows drive mount channel.
Decision: If the failing file is under /mnt/c, treat it as “foreign.” Move build/run artifacts into $HOME if you want fewer surprises.
Task 3: Verify the path is not lying (stat doesn’t lie like your terminal does)
cr0x@server:~$ stat "/mnt/c/Users/alex/Projects/demo/run.sh"
File: /mnt/c/Users/alex/Projects/demo/run.sh
Size: 231 Blocks: 0 IO Block: 4096 regular file
Device: 33h/51d Inode: 0 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1000/ cr0x) Gid: ( 1000/ cr0x)
Access: 2026-02-05 11:34:40.000000000 +0000
Modify: 2026-02-05 11:34:39.000000000 +0000
Change: 2026-02-05 11:34:39.000000000 +0000
Meaning: If stat fails with ENOENT, you truly don’t have a reachable path from Linux’s perspective. If it succeeds, the file exists; execution might still fail for other reasons.
Decision: If stat works but execution fails with “No such file,” jump immediately to shebang and line endings.
Task 4: If executing a script fails, check the first line (shebang)
cr0x@server:~$ head -n1 /mnt/c/Users/alex/Projects/demo/run.sh | cat -A
#!/bin/bash^M$
Meaning: That ^M is a carriage return. Your script’s interpreter path is effectively /bin/bash\r, which does not exist.
Decision: Convert line endings to LF (dos2unix) or configure Git/editor to stop reintroducing CRLF.
Task 5: Confirm line endings and file type with file
cr0x@server:~$ file /mnt/c/Users/alex/Projects/demo/run.sh
/mnt/c/Users/alex/Projects/demo/run.sh: Bourne-Again shell script, ASCII text, with CRLF line terminators
Meaning: This is the classic “it exists but it can’t run” failure mode.
Decision: Fix line endings and commit the fix, or you’ll be re-fixing it every Monday.
Task 6: Convert CRLF to LF safely (and verify)
cr0x@server:~$ dos2unix /mnt/c/Users/alex/Projects/demo/run.sh
dos2unix: converting file /mnt/c/Users/alex/Projects/demo/run.sh to Unix format...
cr0x@server:~$ file /mnt/c/Users/alex/Projects/demo/run.sh
/mnt/c/Users/alex/Projects/demo/run.sh: Bourne-Again shell script, ASCII text
Meaning: Interpreter path is now clean.
Decision: If the script still fails, check execute bit and mount options next.
Task 7: Check execute permissions and mount metadata behavior
cr0x@server:~$ ls -l /mnt/c/Users/alex/Projects/demo/run.sh
-rwxrwxrwx 1 cr0x cr0x 231 Feb 5 11:34 /mnt/c/Users/alex/Projects/demo/run.sh
Meaning: On DrvFs without metadata, permissions can be synthetic; everything may look executable even if policy disagrees. Or the inverse: you chmod +x and it doesn’t stick.
Decision: If execute bit changes don’t persist or behavior differs between machines, inspect /etc/wsl.conf and current mount options.
Task 8: Inspect DrvFs mount options (the root of many “it worked yesterday” cases)
cr0x@server:~$ mount | grep -E ' /mnt/c '
C:\ on /mnt/c type 9p (rw,relatime,dirsync,aname=drvfs;path=C:\;uid=1000;gid=1000;metadata;umask=22;fmask=11;case=off)
Meaning: metadata is present, so Linux mode bits can be stored in NTFS extended attributes. case=off suggests case-insensitive behavior.
Decision: If metadata is missing and you depend on executable bits or symlinks, enable it in /etc/wsl.conf and restart WSL.
Task 9: Validate shebang interpreter existence (it’s not always /bin/bash)
cr0x@server:~$ head -n1 ./run.sh
#!/usr/bin/env bash
cr0x@server:~$ command -v env
/usr/bin/env
Meaning: Using /usr/bin/env is portable, but only if env exists (it usually does) and the target interpreter exists in PATH.
Decision: If command -v bash fails, install it or fix PATH. If env is CRLF-tainted, you’re back to Task 4.
Task 10: If a binary fails with ENOENT, check dynamic loader/architecture
cr0x@server:~$ file ./tool
./tool: ELF 64-bit LSB pie executable, x86-64, dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped
cr0x@server:~$ ldd ./tool | sed -n '1,6p'
linux-vdso.so.1 (0x00007ffc6c5f8000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fda1b0d0000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fda1aeb0000)
/lib64/ld-linux-x86-64.so.2 (0x00007fda1b2f0000)
Meaning: If the interpreter path in file points to something missing, executing the binary can yield “No such file or directory.”
Decision: If ldd says “not found” for any library, install dependencies. If the interpreter path is missing, you’re likely running a binary built for a different distro/architecture.
Task 11: Confirm current working directory and symlink resolution
cr0x@server:~$ pwd -P
/mnt/c/Users/alex/Projects/demo
cr0x@server:~$ readlink -f .
/mnt/c/Users/alex/Projects/demo
Meaning: If your tooling relies on symlinks or relative paths, a different pwd than you expect can break path joins.
Decision: In automation, resolve to absolute paths early and log them. If you’re on /mnt/c, consider relocating execution to Linux filesystem.
Task 12: Check for Windows “smart quotes” or Unicode oddities in filenames
cr0x@server:~$ ls | cat -A
run.sh$
deploy–prod.sh$
Meaning: That filename contains an en dash (–) not a hyphen (-). Your copy-pasted command uses -, so the file is “missing.”
Decision: Rename the file to ASCII-safe names, or at least stop pasting from rich text. Automation hates typography.
Task 13: Test Windows-to-WSL invocation and path translation explicitly
cr0x@server:~$ wsl.exe -e bash -lc 'pwd; ls -l ./run.sh'
/mnt/c/Users/alex/Projects/demo
-rwxrwxrwx 1 cr0x cr0x 231 Feb 5 11:34 ./run.sh
Meaning: When Windows launches WSL, the working directory might not be what your script expects unless you set it.
Decision: In CI or Task Scheduler, always set the working directory explicitly, or pass absolute paths.
Task 14: Convert Windows paths to WSL paths (and validate)
cr0x@server:~$ wslpath "C:\Users\alex\Projects\demo\run.sh"
/mnt/c/Users/alex/Projects/demo/run.sh
cr0x@server:~$ test -e "$(wslpath 'C:\Users\alex\Projects\demo\run.sh')" && echo OK || echo MISSING
OK
Meaning: wslpath is the least-worst way to translate paths, especially when called from Windows contexts.
Decision: Standardize on path translation helpers in scripts rather than hand-rolling string replacements.
Task 15: Diagnose quoting issues with spaces and parentheses (classic interop trap)
cr0x@server:~$ p="/mnt/c/Program Files (x86)/Vendor Tool/bin/tool.exe"; test -e "$p" && echo EXISTS || echo MISSING
EXISTS
cr0x@server:~$ "$p" --version
VendorTool 4.2.1
Meaning: If you don’t quote it, the shell splits on spaces and parentheses become syntax. Then “No such file” is the least confusing part.
Decision: Quote every path. In code review, treat unquoted paths like unescaped SQL: not always exploited, always a risk.
Task 16: Inspect /etc/wsl.conf for mount configuration (the silent policy file)
cr0x@server:~$ sed -n '1,120p' /etc/wsl.conf
[automount]
enabled = true
options = "metadata,umask=22,fmask=11"
mountFsTab = false
Meaning: This controls how Windows drives are mounted. metadata is the big one for permissions; umask/fmask affect default modes.
Decision: If this is missing or inconsistent across machines, standardize it. Drift here causes “works on my laptop” as a service.
Common mistakes: symptom → root cause → fix
1) Symptom: “No such file or directory” when running a script that clearly exists
Root cause: CRLF line endings in the shebang line, resulting in /bin/bash^M.
Fix: Convert to LF (dos2unix file), enforce LF in Git attributes, and make your editor stop “helping.”
2) Symptom: “No such file or directory” only when executed from Windows (Task Scheduler, VS Code task, CI runner)
Root cause: Wrong working directory, or argument quoting changes as the command crosses the Windows↔WSL boundary.
Fix: Use wsl.exe -e bash -lc with explicit cd, pass absolute paths, and translate Windows paths via wslpath.
3) Symptom: chmod +x “works” but execution still behaves inconsistently on /mnt/c
Root cause: DrvFs mount options without metadata (or inconsistent mount policy across machines).
Fix: Configure /etc/wsl.conf to enable metadata; restart WSL. Prefer running build tools on Linux filesystem ($HOME) when possible.
4) Symptom: File exists, but ./tool returns “No such file or directory”
Root cause: Missing dynamic loader or shared library dependency; ENOENT can be thrown for a missing ELF interpreter.
Fix: file tool and ldd tool. Install required libs, or rebuild for the target distro/WSL environment.
5) Symptom: A path works in one repo but not another; same filename, different behavior
Root cause: Case sensitivity mismatch. One directory may be case-sensitive on NTFS; another isn’t. Or tooling uses inconsistent casing in imports/paths.
Fix: Normalize casing in code and build scripts. Avoid relying on case-insensitive behavior. Treat Windows-hosted repos as case-chaos unless proven otherwise.
6) Symptom: “No such file or directory” with paths containing Unicode dashes or smart quotes
Root cause: The filename’s bytes don’t match what you typed. Fancy punctuation looks identical until it ruins your evening.
Fix: Rename files to ASCII-safe names, or copy-paste the exact filename from ls. Prefer machine-generated paths for automation.
7) Symptom: Scripts fail only on network shares mapped into Windows drives
Root cause: Additional translation layer (SMB/CIFS, Windows redirector, then DrvFs). Permissions and file change semantics can differ.
Fix: Copy or sync to Linux filesystem before executing; avoid executing scripts directly from network shares.
8) Symptom: “No such file or directory” when using relative paths in makefiles or npm scripts
Root cause: The tool changes its working directory internally, or you assumed $PWD is stable across interop launches.
Fix: Resolve paths using repository root detection, absolute paths, and log them. In shell scripts, use script_dir="$(cd "$(dirname "$0")" && pwd -P)".
Three corporate mini-stories from the path trenches
Mini-story 1: The incident caused by a wrong assumption
A mid-sized company ran a Windows-based CI runner that executed Linux build steps through WSL.
It had been stable for months, mostly because the team rarely touched the runner images.
Then a new repository landed with a “simple” bootstrap script committed by someone who lived in Windows editors.
The bootstrap script was present. The pipeline logs showed it listed in the directory. But every attempt to run it ended in:
“No such file or directory.” The first responder did what first responders do: re-ran the job. Same result.
Then they tried absolute paths. Same result. Then they copied the file around, like moving a houseplant to see if it stops dying.
The wrong assumption was subtle: everyone assumed ENOENT meant “missing file.” The file wasn’t missing.
The interpreter path was. CRLF in the shebang turned /usr/bin/env into /usr/bin/env\r.
The kernel couldn’t find that interpreter, so it reported ENOENT on executing the script.
They fixed it with line-ending enforcement at the repo boundary, not by telling developers to “be careful.”
A pre-commit hook helped, but the real win was a CI step that failed fast when a shebang contained CR.
It turned a ghost error into a readable one.
Postmortem lesson: assume nothing about “file not found” until you’ve checked the interpreter line.
Computers are literal. People are optimistic. Optimism does not pass builds.
Mini-story 2: The optimization that backfired
Another team tried to speed up their WSL-based dev environment. They kept their repo on /mnt/c
so Windows tools (IDE indexers, antivirus exclusions, file watchers) could access it easily.
But builds were slow, so they did a “smart” thing: run the compilation in WSL but keep the artifacts on /mnt/c
so Windows could pick them up without copying.
The first week looked great. Some workloads got faster, mostly because caches were warm and nobody hit edge cases.
Then a release candidate failed to package. The bundler complained that a generated script was missing.
The script existed, but intermittently “could not be found” during parallel steps.
The backfire wasn’t mystical. They had put a concurrency-heavy workload on a filesystem bridge that didn’t behave like native ext4.
Metadata updates, file notifications, and rename semantics under heavy parallelism exposed timing windows.
The pipeline’s retry logic made it worse by papering over the early signs.
They fixed it by doing the boring thing: move the repo and build outputs into the Linux filesystem ($HOME)
and only copy final artifacts to Windows when necessary. Performance became consistent. Failures stopped being “intermittent.”
Lesson: “fewer copies” is not always “faster.” Sometimes it’s just “closer to the weird part of the system.”
Mini-story 3: The boring but correct practice that saved the day
A regulated enterprise had a mixed fleet: some developers used WSL, some used Linux laptops, and CI ran on Linux.
Their threat model and audit requirements made them allergic to “developer-specific magic.” This was annoying, but it forced discipline.
They standardized a small set of build entrypoints with strict rules:
scripts must use #!/usr/bin/env bash, must be LF, must not assume /bin layout, and must log their resolved paths.
Every repo had a path sanity check that ran in under a second.
One day, a Windows update changed something minor about how a developer launched WSL from their IDE.
A relative path used by a helper script broke—on that developer’s machine only.
The sanity check immediately printed: “Working directory is /mnt/c/…; expected repo root. Aborting.”
They didn’t lose half a day to a ghost hunt. They didn’t “fix” it by telling everyone to reinstall.
They changed the IDE task to cd explicitly and moved on.
Lesson: explicit working directories and early validation are boring. That’s why they work.
Checklists / step-by-step plan
Checklist A: When “No such file or directory” happens executing a script
- Run
stat "path". If it fails, it’s truly a path issue. If it succeeds, continue. - Run
head -n1 file | cat -A. If you see^M, fix line endings. - Run
file file. Confirm it’s a script with LF terminators. - Verify interpreter exists:
command -v bashorls -l /bin/bash. - Check execute bit:
ls -l file. If on/mnt/c, confirm mount options includemetadataif you depend on it. - If invoked from Windows, set working directory explicitly and pass absolute paths.
Checklist B: When “No such file or directory” happens executing a binary
stat ./binaryandls -lto ensure it exists and is executable.file ./binaryto confirm architecture and interpreter path.ldd ./binaryand look for “not found.”- If the interpreter is missing, you’re likely running a binary built for another distro or environment; rebuild inside WSL or match the base image.
- Move the binary to Linux filesystem if it’s on
/mnt/cand behavior is inconsistent.
Checklist C: Standardize WSL mounts to reduce path-related flakiness
- Create/verify
/etc/wsl.confwith automount options appropriate for your org. - Decide whether you want
metadata. If you execute scripts from/mnt/c, you probably do. - Restart WSL (close distros, then restart the WSL service from Windows side).
- Document one blessed location for repos: either Linux filesystem for build/run, or Windows filesystem for editing—but don’t pretend they’re identical.
Exactly one quote, because we’re not here to wallpaper the problem with wisdom posters:
“Hope is not a strategy.” — Gene Kranz
Joke 1: WSL path bugs are like socks in a dryer—eventually everything disappears, and nobody admits touching the settings.
FAQ
1) Why does Linux say “No such file or directory” when the file exists?
Because ENOENT can refer to a missing interpreter (shebang), missing dynamic loader for an ELF binary, or a missing path component after translation.
In WSL, translation layers and mount behavior add more ways to generate ENOENT.
2) How do I quickly tell if it’s CRLF?
Run head -n1 script | cat -A and look for ^M, or run file script and check for “with CRLF line terminators.”
3) Is it bad to keep code under /mnt/c?
It’s not morally bad. It’s operationally risky for Linux-heavy workloads. If you compile, run lots of small file ops, or depend on Unix permissions, prefer $HOME (Linux filesystem).
Keep /mnt/c for interoperability and occasional file exchange, not as your execution engine.
4) Why does it work from WSL terminal but fail when launched from Windows?
Different working directory, different environment variables, different quoting rules, and sometimes different path translation.
Windows launchers often start WSL in a default directory you didn’t expect.
5) What’s the safest way to pass a Windows path into WSL?
Use wslpath to translate and then quote the result. Do not hand-replace \ with / and call it engineering.
6) Why do binaries sometimes fail with “No such file” but ls shows them?
Missing ELF interpreter or shared libraries can throw ENOENT at exec time. Check file for the interpreter path and ldd for missing dependencies.
7) Do mount options really matter for this error?
Yes. Mount options influence permission mapping and metadata storage. Without metadata, you can get confusing “executable” appearances that don’t map cleanly to what you intended.
It’s not always the direct cause of ENOENT, but it frequently contributes to the chain of confusion.
8) Why do paths with spaces break more often in WSL?
They don’t break “in WSL.” They break in shells and in interop boundaries where quoting rules differ.
If you quote every path, you dramatically reduce the failure rate.
9) How do I make this stop happening across a team?
Enforce LF in repos, add a fast sanity check that validates shebangs and path expectations, standardize WSL mount config, and bless one execution location (Linux filesystem).
Also: log absolute paths in build scripts. Debugging without paths is like incident response without timestamps.
10) Is WSL the problem or my scripts?
Usually your scripts. WSL just gives you more ways to accidentally combine Windows assumptions and Linux semantics in one command line.
Treat the boundary like a network hop: validate, normalize, and log.
Joke 2: If your build depends on unquoted paths, you don’t have a pipeline—you have a suspense novel.
Next steps you can actually do this week
- Add a shebang and line-ending gate: a CI step that rejects CRLF in executable scripts and prints the offending line.
- Pick a “build runs here” policy: either in Linux filesystem (
$HOME) or Windows filesystem, but document the tradeoffs and enforce consistency. - Standardize
/etc/wsl.conf: ensure mount options (especiallymetadata) are consistent across dev machines if you execute from/mnt/c. - Make paths boring: avoid Unicode punctuation and rely on ASCII-safe filenames for automation-critical files.
- Log absolute paths: every build entrypoint should print
pwd -Pand the resolved script directory before doing anything destructive.
The “No such file or directory” message isn’t wrong. It’s just not telling you which file it can’t find.
Once you start checking interpreters, mount types, and interop boundaries in that order, the error stops being a mystery and becomes a checklist item.