Repository navigation
Open
Labels
bugSomething isn't workingSomething isn't working
Description
What's wrong
ResolveFileName (RunCommand/RunCommand.cs:345-356) hands two kinds of name to Process.Start unchanged:
- a relative path with a directory part when
WorkingDirectoryis null (the early return on line 347) - any bare name, which also falls back unchanged at
:431when the overlay finds no match
On Unix, .NET's Process.ResolvePath tries, in order:
Path.Combine(dirname(Environment.ProcessPath), fileName)- the current directory
PATH
A shell does neither of the first two for these names.
Case 1: a relative path with no WorkingDirectory
-
Setup:
build.shthat prints "APP-DIR copy" sits indirname(Environment.ProcessPath), andbuild.shthat prints "CWD copy" sits in the current directory. -
Call:
Directory.SetCurrentDirectory(root); await RunCommand.ExecuteAsync("./build.sh", [], new OutputHandler(Console.Write));
-
Observed:
APP-DIR copy, both through the apphost and throughdotnet app.dll. Underdotnet app.dllthe "app dir" is/usr/share/dotnet. -
Expected:
CWD copy, which is whatsh -c ./build.shprints.
Case 2: a bare name runs a file in the caller's current directory before PATH
- Setup: the caller's current directory holds a file named
id, andWorkingDirectory = wdis a different directory. - Mode 755:
ExecuteAsync("id", [], h, new CommandOptions { WorkingDirectory = wd })prints the planted copy's output.sh -c "cd wd && id"runs/usr/bin/id. - Mode 644 (a plain data file): the call throws
Win32Exception: ... trying to start process '<caller>/id' ... Permission denied, while the shell runs/usr/bin/id. This is the same "Permission denied where the shell runs the next match" failure that net5.0/net6.0/netstandard builds pick a non-executable file earlier on the overlay PATH, so the command fails with "Permission denied" instead of running the executable copy later on #139 and Overlay PATH lookup picks a file the current user can't execute (e.g. root-owned 0700), so the start fails with "Permission denied" where the shell runs the next match (net7+) #143 fixed for the overlay PATH, now on the default path.
Why it matters
- What runs depends on the process-wide current directory and on the host's install folder, even when the caller sets
WorkingDirectory, which exists to take that dependency away. - An app that ships scripts as content (
CopyToOutputDirectory) silently runs its bundled copy instead of the project's copy, and reports success. - Any writable file next to the host executable, or in the current directory, is a place to plant a command. This is the Windows current-directory search risk, brought to Unix.
- The README says otherwise. README.md:238 says a bare name "is found the usual way, on the calling process's
PATH", and README.md:223 implies the caller's current directory is what counts whenWorkingDirectoryis unset. - With WorkingDirectory set,
./build.shstill resolves against the caller's current directory: it runs the caller's copy, or fails with "No such file" though the file is in WorkingDirectory #115's body noted the .NET search order in passing and suggested documenting it or resolving it, but neither was done. Resolve a relative executable path against WorkingDirectory [patch] #131 went ahead on the assumption that bare names go to PATH.
Suggested fix
On non-Windows:
- When
fileNamehas a directory part and is not rooted, combine it withWorkingDirectory ?? Directory.GetCurrentDirectory(). Leave the result unnormalized, as Resolve ".." in a relative executable from where a symlinked working directory points [patch] #149 does, so..resolves the way the shell resolves it. - Resolve a bare name on the effective PATH (the overlay
PATHif set, elseEnvironment.GetEnvironmentVariable("PATH")), reusingResolveOnSearchPath/TryExecutable, and pass the absolute result. - If nothing matches, fail the way a shell does ("not found") instead of handing the bare name to .NET.
- Update README.md:223 and :238 to match.
Tests to add:
./x.shwith no WorkingDirectory runs the current directory's copy, not the app-directory copy.- A bare name that exists both on PATH and as a file in the caller's current directory (executable, or not executable) runs the PATH copy.
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working
Activity
matt-edmondson commentedon Nov 12, 2026
Triage
WorkingDirectoryis set.ResolveFileName,ResolveOnSearchPath/TryExecutable) plus README.md:223 and :238../build.shstill resolves against the caller's current directory: it runs the caller's copy, or fails with "No such file" though the file is in WorkingDirectory #115 (which noted the .NET search order but resolved neither way), Resolve a relative executable path against WorkingDirectory [patch] #131, net5.0/net6.0/netstandard builds pick a non-executable file earlier on the overlay PATH, so the command fails with "Permission denied" instead of running the executable copy later on #139/Overlay PATH lookup picks a file the current user can't execute (e.g. root-owned 0700), so the start fails with "Permission denied" where the shell runs the next match (net7+) #143 (Permission-denied handling for the overlay PATH) and Resolve ".." in a relative executable from where a symlinked working directory points [patch] #149 (unnormalized relative paths). Overlay PATH lookup picks a file the current user can't execute (e.g. root-owned 0700), so the start fails with "Permission denied" where the shell runs the next match (net7+) #143 is still open and changes the sameTryExecutablepath, so the two fixes should be coordinated, or landed in one PR.Generated by Claude Code