GitHub Actions unexpected value steps, and the file it usually came from
GitHub Actions unexpected value steps is the validator saying the mapping it was reading has no steps key in it, and the column it reports tells you which mapping that was. At column 1 the file is being read as a workflow root, which nearly always means the file was written for a different CI system and ended up in the workflows directory.

What this error means
Validation fails on a steps: line, and the useful information is the position rather than the key. Read the column first. Column 1 means steps was at the top level of the file, where the workflow root allows only nine keys and this is not one of them; that finding usually arrives beside several others and a complaint that jobs is missing, which together describe a file that is not a GitHub workflow at all. A column deeper in means steps was inside something, and the two somethings that reject it are a job that has already been read as a reusable-workflow caller, and the jobs mapping itself when a job identifier was left out.
Invalid workflow file: .github/workflows/azure-pipelines.yml#L1
(Line: 8, Col: 1): Unexpected value 'trigger', (Line: 18, Col: 1): Unexpected value 'pr',
(Line: 20, Col: 1): Unexpected value 'pool', (Line: 23, Col: 1): Unexpected value 'variables',
(Line: 34, Col: 1): Unexpected value 'steps', (Line: 5, Col: 1): Required property is missing: jobsOne pass, several findings, and what they add up to
Validation does not stop at the first problem. It reads the file through, records every key it could not place, and reports them together, which is why the quoted failure above carries five rejections and a missing-property complaint in one go. Read as a set rather than one at a time, that list is a diagnosis: trigger, pr, pool, variables and steps are all top-level keys in Azure Pipelines, and none of them exist in a GitHub workflow.
The final finding is the one that names the real problem. Required property is missing: jobs says the file has no jobs at all, which no workflow with a genuine indentation slip would ever produce. A file that trips every top-level key and then has no jobs is a file from somewhere else.
Column of the reported steps | Mapping being read | Most likely cause |
|---|---|---|
| 1 | The workflow root | A file written for another CI system, or a workflow with no jobs: line |
| Same as job identifiers | The jobs mapping | A job identifier was omitted, so steps was read as a job name |
| Same as a job's other keys | A job already narrowed to the caller shape | The job has a uses: key |
Common causes
The file belongs to another CI system
An Azure Pipelines, GitLab or CircleCI definition copied or generated into .github/workflows/. Every file in that directory is validated as a GitHub workflow, so a foreign one trips several top-level keys at once and then reports no jobs. The quoted report is precisely this, and the fix was to move the file rather than to edit it.
The job calls a reusable workflow and cannot also have steps
The version that appears in a workflow that was fine yesterday. Somebody added a step to a job that has uses:, and the schema has no shape that allows both. In our experience the intent is almost always a setup or notification step that belongs in a separate job.
A job identifier is missing, so the mapping shape is wrong
Writing steps: directly under jobs: produces a job called steps rather than a rejected key, and the complaints then land on the step entries and the missing runs-on. It is worth knowing so you can recognise when this is not your problem.
A de-indent pulled the block out of its job
The explanation most guidance leads with, and the least common of the four in practice. It is also the easiest to confirm, because the reported column is 1 while the rest of the file uses nested columns, and the rest of the job is still intact above it.
How to fix it
Read the whole finding list, not the first line
- Collect every
Unexpected valuein the report along with the missing-property complaints. - If several top-level keys are rejected and
jobsis missing, the file is not a GitHub workflow; stop editing it. - If only
stepsis rejected, take its column and match it against the table above.
Move foreign CI files out of the workflows directory
A definition for another system belongs wherever that system reads it, not under .github/workflows/. Move it, and if the pipeline is genuinely wanted on GitHub, write a workflow for it rather than translating the file in place.
git mv .github/workflows/azure-pipelines.yml azure-pipelines.ymlGive the extra work its own job
A caller job cannot carry steps, so put them in a normal job and order the two with needs. This also makes the dependency explicit, which interleaved steps never did.
notify:
needs: build
if: always()
runs-on: ubuntu-latest
steps:
- run: ./scripts/notify.sh "${{ needs.build.result }}"Put a job identifier between jobs and steps
- Confirm there is a name under
jobs:and thatruns-onandstepsare indented inside it. - Remember that
jobstakes any identifier, so a mistake there is accepted rather than rejected. - Re-read the report afterwards: a different set of findings means you have moved on to the next problem.
Why a caller job refuses steps too
A job in the published schema is a choice between two shapes: the ordinary job, which requires runs-on and allows steps, and the caller job, which requires uses and allows neither. The reader narrows between them as it goes, so a job that has already shown a uses: key is committed to the caller shape by the time it reaches steps.
This is the version of the error that appears in an otherwise healthy workflow, and it usually arrives when somebody adds a setup or notification step to a job that calls a reusable workflow. There is no way to interleave steps around a uses: job, and the message is the schema saying so. The work has to move into the called workflow, or into a separate job.
prepare:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
build:
needs: prepare
uses: ./.github/workflows/build.ymlA missing job identifier turns steps into a job name
The jobs mapping does not have a fixed set of keys. It accepts any identifier and expects a job underneath each one, which means a steps: written directly under jobs: is not rejected as a key at all; it is accepted as a job called steps, and the sequence of steps beneath it is then read as that job's body.
That is why this case produces different wording from the other two. The complaints land on the step entries and on the missing runs-on, not on the word steps. A report naming steps at the same column as your job identifiers means something narrower: the mapping being read was jobs and something about the identifier itself did not work.
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5The same rule one level down, at the step
Steps are narrowed the same way and it is worth recognising because the message looks identical. A step is either a run step, which allows shell and working-directory, or a uses step, which allows with. Mixing them gets an unexpected-value complaint on whichever key came second.
The most common instance of it has its own page, because working-directory on a uses step is widely believed to be accepted and quietly ignored when in fact the file is rejected before anything runs: working-directory on a uses step.
What is published, and what is not
The message that reaches the Actions tab is written by GitHub's pre-run validation service, which is closed, so nothing here describes what that service runs. The template Unexpected value '{0}' is one line in the runner's generated string resources, and the key lists it is applied against are in the workflow schema the runner ships. Those are what this page rests on.
The reports agree with them, which is the reason to trust the account. A file of Azure Pipelines YAML rejecting exactly its own top-level keys, and a caller job rejecting exactly the keys the caller shape omits, are what the published schema predicts in both cases.
Why there is no recorded run on this page
There is no run to record. The file is rejected before any job is created, so nothing is scheduled, nothing is dispatched and no runner is involved; a reproduction on our infrastructure would produce an identical rejection with our repository name on it and no machine in the picture at all. What would be genuinely useful, a file that trips several findings at once, already exists in the public report quoted above, and it is more instructive than anything we would write on purpose because nobody set out to produce it.
How to prevent it
- Keep
.github/workflows/for GitHub workflows only, and move other systems' definitions out on day one of a migration. - Treat a caller job as a job with no body, so nobody tries to add steps to it.
- Attach the workflow schema in your editor, which catches the shape while you type.
- Read the whole finding list in review, because a set of rejections often says more than any single one.
Frequently asked questions
Why is steps rejected at the top level of my workflow file?
on, name, description, run-name, defaults, env, permissions, concurrency and jobs. Steps live inside a job, and a job lives inside jobs. A steps: at column 1 is outside all of that.Can I add a step to a job that uses a reusable workflow?
uses does not allow steps. Move the work into the called workflow, or into a separate job ordered with needs.My whole file is rejected key by key. What does that mean?
.github/workflows/. Validation reports every unplaceable key from one pass, so a foreign definition trips several at once and then reports that jobs is missing, which no ordinary workflow mistake produces.What happens if I put steps directly under jobs?
steps, because jobs takes any identifier rather than a fixed key list. The complaints then land on the step entries and on the missing runs-on, not on the word steps, so that report looks different from this one.Related guides
References
- actions/runner: workflow-v1.0.json, the workflow root, jobs, job and steps definitions
- actions/runner: TemplateStrings.g.cs, the Unexpected value template
- actions/runner: TemplateReader.cs, the mapping reader that reports an unplaceable key
- Linetec-Services-LLC/Generate-Weekly-PDFs-DSR-Resiliency#12: the multi-finding report quoted here
- GitHub Actions documentation