Skip to content
Latchkey LogoLatchkey home

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.

Three columns a steps key can be reported at, and the mapping each one means
The allowed key lists come from the shipped workflow-v1.0.json. The framed lines are quoted from Linetec-Services-LLC/Generate-Weekly-PDFs-DSR-Resiliency#12.

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.

Actions log, quoted from Linetec-Services-LLC/Generate-Weekly-PDFs-DSR-Resiliency#12
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: jobs

One 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 stepsMapping being readMost likely cause
1The workflow rootA file written for another CI system, or a workflow with no jobs: line
Same as job identifiersThe jobs mappingA job identifier was omitted, so steps was read as a job name
Same as a job's other keysA job already narrowed to the caller shapeThe 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

  1. Collect every Unexpected value in the report along with the missing-property complaints.
  2. If several top-level keys are rejected and jobs is missing, the file is not a GitHub workflow; stop editing it.
  3. If only steps is 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.

Terminal
git mv .github/workflows/azure-pipelines.yml azure-pipelines.yml

Give 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.

.github/workflows/ci.yml (illustrative)
  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

  1. Confirm there is a name under jobs: and that runs-on and steps are indented inside it.
  2. Remember that jobs takes any identifier, so a mistake there is accepted rather than rejected.
  3. 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.

.github/workflows/ci.yml (illustrative)
  prepare:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

  build:
    needs: prepare
    uses: ./.github/workflows/build.yml

A 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.

.github/workflows/ci.yml (illustrative)
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

The 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?
Because the workflow root allows only nine keys: 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?
No. The published schema gives a job two shapes, and the one that requires 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?
Usually that the file was written for another CI system and copied into .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?
It is accepted as a job named 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

Porting a pipeline is the moment to pick the runner. Latchkey is $0.0025/min at 2 vCPU. Start free → 30-day trial · No credit card