Skip to content
Latchkey LogoLatchkey home

GitHub Actions working-directory on a uses step is rejected, not ignored

GitHub Actions working-directory on a uses step is refused by the workflow schema before anything runs, so the belief that the key is accepted and quietly ignored is backwards. There is no wrong directory to debug, because the file never compiled and no job was ever created.

Two step shapes in the schema and the keys each one accepts, with the rejected key marked
A step is one of two shapes. Reading `uses` narrows the reader to the shape that has no `working-directory` property, and the next key has nowhere to go.

What this error means

You added a directory to a step that calls an action, pushed, and the workflow did not run. In the Actions tab the file is flagged rather than the job, and the annotation names a line and a column and quotes the key you added. If you are reading a stale answer that says the key is silently ignored, the thing to check is whether your run exists at all: an ignored key produces a green run that did the wrong work, and a rejected key produces no run.

Message text from TemplateStrings.UnexpectedValue in actions/runner
Invalid workflow file: .github/workflows/ci.yml#L12
(Line: 12, Col: 9): Unexpected value 'working-directory'

A step is one of two shapes, and `uses` picks one

In the published workflow schema, an item in a job's steps sequence is a one-of between two mapping definitions, a run step and a regular step. The run step requires run and lists working-directory and shell among its properties. The regular step requires uses and lists with and env, and it has no working-directory property at all. Three independently maintained copies of that schema agree: the two in actions/runner and the one the editor extension validates against in actions/languageservices.

The reader does not evaluate both shapes and pick a winner at the end. It narrows as it reads. Each key is looked up across the candidate definitions still in play, and when a key exists in some of them and not others, the ones that do not have it are dropped from the list. Reading uses therefore removes the run step from consideration immediately.

The next key is then looked up against a single remaining definition. working-directory is not a property of it, the step shape allows no loose keys, and the reader records an error naming the key. The message is the schema reader's generic one for a key it has no home for, which is why it does not mention actions or directories.

Where you write working-directoryIn the schemaWhat it governs
On a step that has runa property of the run stepthe shell's working directory
On a step that has usesno such propertynothing, the file is refused
Under defaults.run on the workflowa property of the run defaultsevery run step in the file
Under jobs.<id>.defaults.runa property of the job run defaultsevery run step in that job

Common causes

The key was copied from a run step onto a uses step

Overwhelmingly the commonest origin. Somebody had a working run step with a directory on it, replaced the command with an action, and kept the surrounding keys. The diff looks harmless because only the middle line changed.

An answer elsewhere said the key is ignored rather than refused

The claim that a uses step accepts working-directory and quietly does nothing with it is widespread and wrong. It matters because the two behaviors need opposite responses: an ignored key means hunting for why the action used the wrong path, and a refused key means the run you are looking for does not exist.

The action really has no way to take a path

Some actions assume the repository root and offer no input for anything else. Reaching for working-directory is the natural next move, and the schema refuses it, which leaves the reader convinced the key is broken rather than absent.

The directory belonged on the job, not the step

When every step in a job works in the same subdirectory, per-step keys accumulate until one of them lands on an action. In our experience this is the version that survives review, because each individual line looks like the line above it.

How to fix it

Pass the path as an input the action declares

Open the action's action.yml and read the inputs block. Most actions that touch the filesystem expose a path, a working-directory or a source input. Passing it under with is the only mechanism the step shape offers, and it is the one the action author tested.

.github/workflows/ci.yml (illustrative)
- uses: some-org/build-action@v1
  with:
    path: packages/app

Set the directory once for the job, for run steps

Move the directory to defaults.run.working-directory on the job. It applies to every run step in the job and to none of the actions, which is exactly the boundary the schema draws, so nothing is left ambiguous for the next reader.

.github/workflows/ci.yml (illustrative)
jobs:
  build:
    defaults:
      run:
        working-directory: packages/app

Wrap the action in a run step when it takes no path

  1. Check the action's action.yml once more for any input that accepts a path.
  2. If there is none, replace the action with the command it wraps, in a run step that carries the directory.
  3. If the action does real work you do not want to reimplement, prepare the directory in a run step before it, so the action still sees the layout it expects at the repository root.

Split the job when two directories are genuinely involved

A job whose steps work in two places is asking one defaults block to be two values. Two jobs, each with its own default, expand into a clearer run page and let you give them different conditions later. The cost is one extra checkout, which a cache usually absorbs.

Where the message belongs, and what your copy of it proves

Unexpected value is one of the most common annotations GitHub produces, and it is raised from three different places in the reader for three different findings. Our page on the Unexpected value annotation is the one that explains how to read the file, line and column prefix, and how to tell a rejected key from a rejected value. This page is only about one of those branches.

The branch here is the key branch, in the routine that reads a mapping with well known properties. It runs after the candidate definitions have been narrowed, after a check for a duplicate key, and after a check for loose keys, which a step shape does not permit. So your annotation is telling you something quite precise: the parser had already decided which kind of step you were writing, and the key you added was not part of it.

The column number is worth reading. It points at the key, not at the value, which is a fast way to confirm you are in this branch rather than in the one where a value fell outside an allowed set.

What to do instead, in order of preference

Most actions that operate on a directory take it as an input, because the author had to solve this problem too. Read the action's own action.yml rather than its README: the inputs block is the list of things it will actually accept, and README examples go stale. If the action takes a path, pass it under with.

If the action has no path input, the action was probably not designed to be pointed at a subdirectory, and wrapping it is the honest fix. A run step that changes directory and does the work, or a run step that moves what the action needs into place before the action runs, is clearer than fighting the step shape.

The third option is to stop asking one job to work in two places. A job whose defaults.run.working-directory is the subdirectory, containing steps that all belong to that subdirectory, is easier to read than a job with per-step directories, and it costs nothing on a matrix because the default can come from the matrix value.

.github/workflows/ci.yml (illustrative)
jobs:
  build:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: packages/app
    steps:
      - uses: actions/checkout@v7
      - run: npm ci
      - run: npm run build

Why there is no recorded run on this page

The failure described here happens while the workflow file is being read, and the observable consequence is that no run is created. There is nothing for a runner to do, no job to queue and no log to capture, so a recorded Latchkey run would have to be a recording of a different workflow that did compile. The artifact this page is about is an annotation on a file, and the text of that annotation is a format string in the published parser, which is quoted above rather than reproduced.

The composite action page in this batch shares the first half of this mechanism and spends its space on the part that does reach a runner, so the two do not make the same argument twice.

How to prevent it

  • Validate workflow files in the editor, where the same schema is applied before you push.
  • Keep directory handling on the job's defaults.run rather than on individual steps.
  • Read an action's action.yml, not its README, before deciding how to pass it a path.
  • When a workflow stops running at all, look for an annotation on the file before looking at jobs.

Frequently asked questions

Does working-directory work on a uses step in GitHub Actions?
No, and it is not ignored either. The schema defines a step as one of two shapes, and the shape that requires uses has no working-directory property. Writing it there makes the workflow file invalid, so no run is created at all.
How do I make an action run inside a subdirectory?
Pass the directory as an input the action declares, if it has one. If it does not, do the work in a run step that carries working-directory, or move what the action needs into place before calling it. There is no step-level key that redirects an arbitrary action.
Does defaults.run.working-directory apply to actions?
No. The block is named run because it sets defaults for run steps, and the runner only consults it when it is preparing a script step. An action decides its own working directory, usually from an input or from the repository root.
Why does the annotation say Unexpected value instead of naming the step?
Because the message comes from the generic schema reader rather than from anything that knows about actions. It is raised when a key has no matching property in the definition the reader narrowed to, and it quotes the key and the position. Our page on that annotation covers how to read the file, line and column prefix.

Related guides

References

Refused, not ignored: no run, no runner, no bill. Latchkey prices the real ones at $0.0025/min. Start free → 30-day trial · No credit card