GitHub Actions step cannot have both uses and run, and what you get
A GitHub Actions step cannot have both uses and run, and that sentence was once the message you got, but the parser that ships today does not contain it anywhere. What it produces instead names one of the two keys as unexpected, which is a different sentence and a more precise one once you know how the reader arrives at it.

What this error means
The workflow stops running and the commit carries an invalid file annotation with a line, a column and a quoted key. The quoted key is one of the two you wrote, and which one it is depends on the order you wrote them in rather than on which one is wrong. If you came here from an older answer expecting a sentence about both keys, the thing to check is whether your annotation says that at all, because a message that quotes a single key is telling you something the older sentence did not.
Invalid workflow file: .github/workflows/ci.yml#L9
(Line: 9, Col: 9): Unexpected value 'run'What the reader does with the second key
In the published schema an item in a step sequence is a choice between two mapping shapes. One requires the run key and additionally allows a working directory and a shell. The other requires the uses key and additionally allows with. Everything else the shapes accept, env among it, is on both. Neither shape lists the other required key, and neither permits loose keys.
The reader does not collect your keys and judge them at the end. It narrows while it reads. Each key is looked up across the shapes still in play, and when a key exists in some of them and not others, the ones without it are removed from the list. Reading uses therefore deletes the run shape immediately, and reading run deletes the uses shape.
Whichever of the two you wrote first wins that race. The second key is then looked up against a single surviving shape which does not have it, there are no loose keys to absorb it, and the reader records an error naming the key. The format string it uses is the generic one for a key with no home, which is why the message says nothing about steps, actions or commands.
That message belongs to a page of its own, since it is raised from several places for several findings. Our page on the unexpected value annotation is where to read about the file, line and column prefix and how to tell a rejected key from a rejected value. This page is only the branch where a step was narrowed to one shape and then given a key from the other.
| What the step contains | Shapes still in play at the end | What the reader records |
|---|---|---|
uses then run | the uses shape only | unexpected value naming run |
run then uses | the run shape only | unexpected value naming uses |
| Neither key | both shapes | a message asking for one of the missing properties |
uses with with and env | the uses shape only | nothing, this is valid |
Common causes
Indentation merged two steps into one item
The commonest cause and the hardest to see. A missing dash turns what should be a new sequence item into another key of the previous one, and the file stays valid YAML throughout.
A command was replaced by an action in place
Somebody swapped the middle of a step, putting an action reference where a command was, and left the other key behind. The diff touches one line, which is why review passes over it.
An example was pasted inside an existing step
Documentation examples begin with a dash and assume they are being added as a new item. Pasted into the body of an existing step they become extra keys of it.
The step was meant to do two things
Occasionally the intent really was to run an action and then a command. In our experience this is rarer than the accidental cases, and the answer is the same: two steps.
How to fix it
Read which key the annotation names
- Open the annotation and note the quoted key and the column.
- The quoted key is the second one in your file, not necessarily the wrong one.
- Go to that line and look at whether it should have started a new sequence item.
Split into two steps
Give each of the two a dash of its own at the same indentation as the other items in the sequence. Name them if the job has several steps, since the default name of a run step is the command text.
steps:
- name: Check out
uses: actions/checkout@v5
- name: Install
run: npm ciCheck the whole sequence, not just the reported line
An indentation slip that merged one pair often merged another further down, and the reader stops recording after it has enough to reject the file. Read every item in the sequence once before pushing again.
Validate before pushing
The same schema is available to editor tooling, so a file that will be rejected can be rejected locally. This is the only one of these fixes that prevents the next occurrence rather than resolving this one.
The sentence this page is named for
The wording about a step not being able to have both keys is real in the sense that people met it, and it is old. We looked for it as an exact phrase, which is safe to do here because it carries no numeral and so is not damaged the way a phrase containing a number would be. The reports we fetched and searched that genuinely contain it cluster in the period before 2021, and the modern results we checked mostly do not contain the phrase in their bodies at all.
It is also absent from the sources you can read. The message catalog that the template reader draws from lists the generic messages by name, and there is no entry for anything about uses and run. Neither the runner nor the language services parser contains the sentence, and a code search across GitHub turns it up only in third party linters that wrote their own wording, and in this hub own retired corpus.
The honest summary is therefore in two parts. A validation component GitHub does not publish once produced that sentence, and we cannot say what that component does today because we cannot read it. The parser that is published produces the message quoted above, from the branch described above. If your annotation carries the older sentence, you have learned something about GitHub that we could not establish from the outside, and the fix is identical either way.
Splitting the step, and the indentation that merged it
The fix is always the same: one step does one thing. Put the action in its own item with uses and the command in its own item with run. Nothing is lost by the split, because the two were never going to run as one thing anyway.
The more useful question is how they got merged, and the answer is usually indentation rather than intent. In a sequence, a new item starts with a dash. A line that should have begun a new item and instead sits at the same indentation as the keys of the previous one becomes another key of that item. The diff looks tiny and the YAML stays valid, which is why this survives review more often than a mistake this simple should.
The version that catches experienced people is a partial edit. Replacing the command in a step with an action, and leaving the run line above or below it, produces exactly this. So does pasting an example from documentation into the middle of an existing step rather than after it.
# merged: run became a second key of the first item
steps:
- uses: actions/checkout@v5
run: npm ci
# split: two items, each a step
steps:
- uses: actions/checkout@v5
- run: npm ciWhy there is no recorded run on this page
The file does not compile, so no run is created, no job is queued and no runner is involved. There is no log to capture because there was never a process. What exists is an annotation on a commit, and its text is a format string in the published parser, which is quoted here rather than photographed.
It is worth being explicit that the block above is reconstructed rather than copied from somebody run. The position in it is illustrative and the message text is assembled from the generic format string with the key that the branch would pass to it. Saying so matters on a page whose whole subject is that a remembered message and the current one are different sentences.
There is nothing to repair. A step that cannot be narrowed to one shape is an ambiguity in your file, and no runner can guess which half you meant.
How to prevent it
- Keep one action or one command per step, and treat that as a review rule.
- Watch for a missing dash whenever a step gains a key it did not have.
- Paste documentation examples after a step, never inside one.
- Validate workflow files in the editor against the published schema.
Frequently asked questions
Why does the annotation name only one key?
Is the message about both keys still produced?
What happens if a step has neither uses nor run?
Can a step run an action and then a command?
Related guides
References
- actions/runner: the published workflow schema, run-step and regular-step
- actions/runner: TemplateReader.cs, the narrowing loop and the key branch
- actions/runner: TemplateStrings.resx, the message catalog the reader draws from
- GitHub Actions: workflow syntax, jobs.<job_id>.steps
- GitHub Actions documentation