GitHub Actions working-directory does not exist on the runner
When a GitHub Actions working-directory does not exist, the step fails while the runner is starting the shell process, before a single line of your script is read. The wording you get is not an Actions message at all, which is why searching for the phrase you expected returns nothing useful.

What this error means
The step turns red almost instantly, with a single line of output and no trace of your script. There is no command echo, no group header for the run, and nothing from the program you were trying to invoke, because the failure happens while the process is being created rather than after it starts. The message names the interpreter, quotes an absolute path inside the workspace, and ends with an operating system error. The path in it is usually correct in the sense that it is the path you asked for; what is wrong is that nothing has created it yet. The step is often the first in a job, or the first after a job-level default was introduced, and it commonly runs before the repository has been checked out.
An error occurred trying to start process '/usr/bin/bash' with working directory '/home/runner/work/Vellum-KB/Vellum-KB/app'. No such file or directoryA minimal workflow that produces it
This job is written for this page and has never been run. It sets a working directory for every run step in the job and then puts a guard step before the checkout, which is a sensible thing to want: fail fast on a bad input before spending time cloning.
The guard never gets to run. The default applies to it like it applies to everything else, and on a fresh runner the workspace is empty, so the directory named by the default does not exist yet. This is exactly the shape reported in enterprise-onboarding-project#191. Vellum-KB#584 is the same failure one step out: there the default sits at workflow level and the job it breaks has no checkout step in it at all.
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: app
steps:
- name: Confirm the target
run: test "${{ inputs.confirm }}" = "yes"
- uses: actions/checkout@v7
- run: npm ciCommon causes
A job default applied to a step that runs before checkout
The clearest version, and the one in both reports cited here. A default at job or workflow level applies to every run step underneath it, including guards, confirmations and anything else deliberately placed before the repository arrives. On a fresh runner the workspace exists and is empty, so the root resolves and the subdirectory does not.
The directory is generated by a step that has not run yet
A build output, an extracted archive or a directory created by a generator. The path is right and the ordering is wrong, and moving the step one position later fixes it without touching the path at all.
The relative path has one segment too many or too few
Relative values are resolved against the workspace root, which is the repository root after a default checkout. A path copied from a local shell that was already inside a subdirectory therefore gains a level that does not exist on the runner.
Case or spelling differs from the repository
Linux runners are case sensitive and macOS runners are usually not, so a path that works on a developer machine and on one runner image can fail on another. In our experience this is the one that survives review, because the value looks correct to everyone reading it.
How to fix it
Give the pre-checkout steps a directory that always exists
The workspace root is created before the job starts, so naming it explicitly overrides the default for the one step that needs to run early. This is the fix applied in enterprise-onboarding-project#191, and it keeps the guard first rather than moving it after the clone.
steps:
- name: Confirm the target
working-directory: ${{ github.workspace }}
run: test "${{ inputs.confirm }}" = "yes"
- uses: actions/checkout@v7
- run: npm ciMove the default down to where it is true
A job-level default is a claim that every run step in the job belongs in that directory. When that is not true, put the value on the steps that need it, or split the job so each half has an honest default.
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install
working-directory: app
run: npm ci
- name: Build
working-directory: app
run: npm run buildPrint the workspace once and compare it with the message
- Add a temporary step with no working directory that lists the workspace.
- Compare the entry you expected against the path quoted in the failing message, character for character.
- Check the case of every segment, since the same value can work on macOS and fail on Linux.
- Delete the step once the path is right.
- name: Show the workspace
run: ls -la "$GITHUB_WORKSPACE"Create the directory before anything changes into it
When the path is produced by the job rather than by the repository, make it in a step that has no working directory of its own. A step cannot create the directory it is trying to start in, because the process fails before the script runs.
- name: Prepare the output tree
run: mkdir -p dist/reports
- name: Write the report
working-directory: dist/reports
run: ./gen-report.shThe message is not from GitHub Actions
This is the detail that makes the failure hard to search for. The runner never checks whether the directory is there. It works out a path, puts it on the process start information, and asks the platform to create the process; when that fails, the exception carries a message composed by the .NET class library, and the runner prints it.
The format string is in System.Diagnostics.Process in dotnet/runtime, with three slots: the executable, the directory, and the operating system error. On Linux the third slot is the text for the underlying errno, which is where "No such file or directory" comes from. Knowing this is practical: it means the phrase to search for is the start of the sentence, not any wording about a working directory being invalid.
// dotnet/runtime, System.Diagnostics.Process Strings.resx
An error occurred trying to start process '{0}' with working directory '{1}'. {2}
// ProcessUtils.cs, which fills the middle slot
string directoryForException = string.IsNullOrEmpty(workingDirectory) ? Directory.GetCurrentDirectory() : workingDirectory;How the path in the message is built
The runner takes the value from the step, falls back to the job defaults when the step has none, and combines it with the workspace path from the repository context. The combination is a plain path join, which has one behavior worth knowing: an absolute second argument replaces the first rather than being appended to it.
That gives the resolution table below. Everything else is unchanged from what you wrote, so the path in the message is a faithful report of what the runner was asked for, and the question is only ever whether something has created it.
| What the step or defaults say | Path the runner asks for |
|---|---|
| nothing | the workspace root |
| app | the workspace root, then app |
| ./app | the workspace root, then app |
| ../app | the parent of the workspace root, then app |
| an absolute path | that absolute path, workspace ignored |
| ${{ github.workspace }} | the workspace root |
The string you were probably searching for does not exist
There is no GitHub Actions error reading that a working directory does not exist. The documentation phrases the requirement as advice rather than as a message, in a tip beside the key: "Ensure the working-directory you assign exists on the runner before you run your shell in it."
So the page you want is this one, the message you have is the .NET one above, and any write-up quoting an Actions-branded sentence about a missing working directory is quoting something that was never emitted. It is also worth separating this failure from the one where the key appears to do nothing: a working directory on a step that uses an action rather than a script is ignored, and that is a different page.
Why there is no recorded run on this page
This one does happen on a runner, which makes it worth saying clearly why there is still no log here. The library records a run when we have reproduced a failure on our own hardware and kept the output, and this batch has none. Nothing about the failure is transient, either: the directory is absent because of the order of the steps, so a retry produces the same line and there is nothing for a runner to repair. The quoted line comes from a public repository and is attributed on the block.
How to prevent it
- Keep pre-checkout steps out of any job that sets a working directory default.
- Prefer a working directory on the steps that need one over a default on the whole job.
- Create generated directories in an earlier step, never in the step that runs inside them.
- Match the case of every path segment to the repository, since one runner image will forgive it and another will not.
Frequently asked questions
Why does my step fail before printing anything at all?
Does a job-level working-directory default apply before checkout?
Is a working-directory value relative to the repository or the workspace?
Which error text should I search for when this happens?
Related guides
References
- GitHub Actions: workflow syntax, jobs.<job_id>.steps[*].working-directory
- dotnet/runtime: the process start error string and where it is composed
- actions/runner: ScriptHandler.cs, how the working directory is resolved
- Vellum-KB#584: a job default applied to a step with no checkout
- enterprise-onboarding-project#191: a job-level default reaching a guard placed before checkout
- GitHub Actions documentation