Skip to content
Latchkey LogoLatchkey home

GitHub Actions on section missing from a workflow file

A GitHub Actions on section missing from a workflow file ends the run before it starts, and the layer that refuses it is not the one most people assume. The published schema marks only the jobs key as required at the workflow root, so the refusal comes from GitHub rather than from anything in the parser you can read.

The workflow root keys, which the published schema requires, and where the trigger check happens
The published schema requires only jobs at the workflow root. The trigger requirement is applied by GitHub, outside the parser that ships in the runner.

What this error means

Every push to every branch produces a failed check against a workflow you did not think was running. That is the shape worth recognizing, because a workflow with no usable trigger does not quietly sit there: it is evaluated, found unusable and reported, on every push, forever. The file is often one somebody meant to disable. Commenting the whole thing out, emptying it while leaving it in place, or deleting the contents and keeping the name all produce the same result, which surprises people who expected an empty file to be ignored.

Quoted from susam.net, Minimal GitHub Workflow, a recorded experiment on a zero-byte workflow file
Error
No event triggers defined in `on`

What the published schema actually requires

Follow the workflow root definition in the schema that ships in the runner and the list of properties is short: the trigger key, a name, a description, a run name, defaults, env, permissions, concurrency and jobs. Exactly one of them carries the required flag, and it is jobs. The trigger key is listed as an ordinary optional property.

The editor copy of the schema in the language services repository has the same root definition with the same single required property, which is why an editor extension does not warn you about a file with no trigger. Both published parsers will read such a file without objecting.

The refusal therefore happens somewhere else. GitHub evaluates the file when it decides which workflows a push affects, and a file that offers no usable event is reported at that point. That component is not published, so the honest statement is that the rule is GitHub behavior rather than parser behavior.

The message it produces is the one quoted above, and what it leaves out is the diagnosis. There is no file path, no line and no column, which is unusual for this validator: the key is not there to point at. A recorded experiment that walked a workflow file up from zero bytes got those two lines from the empty file, and something else from the next rung, where the key exists with nothing under it. That rung gets the ordinary shape, a position and an unexpected empty value, because an empty value is a value. So the distinction that matters is whether the key survived, and parentheses and numbers in your annotation mean it did.

The rest of this page is the other half: why the file looks fine to every tool you have locally, and which edits produce it.

LayerRequires a triggerWhat it does with a file that has none
YAML parsers generallynoparses it, no complaint
The published workflow schemano, only jobs is requiredreads it, no complaint
The editor schema copyno, the same root definitionno warning in the editor
GitHub, when deciding what a push runsyesreports it against every push

Common causes

The trigger block was commented out to disable the workflow

The commonest origin by a distance, and the one place the wording of the edit decides which message you get. Comment out the key along with its events and no key is left, which is this message. Comment out only the events, as below, and the key survives with nothing under it, which the ladder answers differently. Either way the file fails against every push rather than doing nothing, which is the opposite of the intent.

Illustrative workflow fragment
# the trigger key survived, every event under it did not
on:
#  push:
#    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: echo hello

The file was emptied but not deleted

A commit that strips the contents and leaves the path in place. The diff reads as a deletion in review, and the zero byte file is still evaluated on every push.

The trigger key drifted out of the root mapping

One extra level of indentation puts it inside another key. The YAML is valid, the block looks right in isolation, and the root genuinely has no trigger.

The file is not a workflow at all

A template, a fragment or an editor configuration file stored in the workflows directory. Everything in that directory is treated as a workflow, so a file that was never meant to be one fails as one.

How to fix it

Decide whether the workflow should exist

  1. If it should run, restore a trigger block at the root of the file.
  2. If it should not run for now, disable the workflow rather than editing it.
  3. If it should never run again, delete the file.
  4. If it is not a workflow, move it out of the workflows directory.

Restore a trigger at the root, not inside another key

Check the indentation of the trigger key against the jobs key. Both belong at column one, and a trigger that is indented further is inside something else regardless of how correct the block under it looks.

.github/workflows/ci.yml
on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest

Disable rather than comment out

Disabling leaves the file intact and stops it being considered, which is what commenting out the events was trying to achieve. It is reversible from the same command.

shell
gh workflow disable ci.yml
gh workflow enable ci.yml

Keep non workflow files out of the workflows directory

Templates and fragments belong beside it, not in it. A directory whose every file is a workflow is one fewer thing to explain to the next person who adds a file there.

The quoting advice, and why it does not apply here

There is widespread advice to quote the trigger key because YAML will otherwise read it as a boolean. That advice has a real origin and it does not apply to this parser, which is worth stating plainly because acting on it wastes time you could spend on the actual cause.

The runner YAML reader resolves plain scalars using the YAML 1.2 core schema, and the source says so in a comment with a link to the specification. Its boolean matcher accepts six spellings and no others: true, True, TRUE, false, False and FALSE. The words on, off, yes and no are not among them, so the trigger key is read as the ordinary string it looks like.

The advice comes from YAML 1.1, where those four words are booleans, and from the many tools that still implement it. A script that loads your workflow with a 1.1 parser really does see a key that is not the trigger key, and linters warn about it for that reason. That is a fact about your tooling, not about GitHub, and quoting the key is harmless but it will not fix a missing trigger.

YAML 1.2 core schema, as implemented in the runner reader
# the runner reads these six spellings as booleans, and no others
true   True   TRUE   false   False   FALSE

# so this key is a string to the workflow parser
on:
  push:
    branches: [main]

The edits that produce it

Commenting out the trigger block is the first. Somebody wants to stop a workflow temporarily, comments out the events and leaves the key, and now the file has a trigger key with nothing under it. Reports of this describe a failure on every push to every branch, which is exactly what it should do, and it is startling because the intent was to make the workflow stop.

Emptying the file is the second. A commit that removes the contents of a workflow and leaves a zero byte file behind produces the same thing, and the diff looks like a deletion, so reviewers read it as one.

Indentation is the third and the least obvious. A trigger key that has drifted one level in, so that it sits under something else rather than at the root, is a perfectly valid mapping in the wrong place. The root then has no trigger key, and the misplaced one is a stray key inside whatever now contains it.

The remedy for all three is the same and it is not to edit the trigger block. If you want a workflow to stop running, delete the file or disable the workflow, which are both operations that leave nothing behind to be evaluated on every push.

shell
# stop a workflow without leaving a file that fails on every push
gh workflow disable ci.yml

# or remove it entirely
git rm .github/workflows/ci.yml

Why there is no recorded run on this page

A workflow with no usable trigger does not produce a run with jobs in it, so there is no log to capture. The observable artifact is a failed check attached to a commit, and a capture of ours would show our repository name and our commit, which carries no information about yours.

The parts of this page that can be established from published sources are established from them: the single required property at the workflow root is read out of both copies of the schema, and the six boolean spellings are read out of the reader that implements the YAML 1.2 core schema. The part that cannot be, which is what GitHub does with the file, is labeled as GitHub behavior rather than dressed up as parser behavior.

Nothing here is repairable by a runner. No runner is ever handed this workflow, because the decision is made before any job is created.

How to prevent it

  • Never disable a workflow by commenting out its trigger block.
  • Treat an emptied workflow file in a diff as a file that still runs.
  • Keep only real workflows in the workflows directory.
  • Check that the trigger key and the jobs key sit at the same indentation.

Frequently asked questions

Is the on key required in a GitHub Actions workflow?
Not by the published schema, which marks only the jobs key as required at the workflow root, and the editor copy of that schema agrees. The requirement is applied by GitHub when it decides which workflows a push affects, which is why no local tool warns you.
Do I need to quote the on key so YAML does not read it as true?
Not for GitHub. The runner reader follows the YAML 1.2 core schema and its boolean matcher accepts only true, True, TRUE, false, False and FALSE. The advice comes from YAML 1.1 parsers, which many other tools still use, so quoting helps those tools and changes nothing here.
Why does an empty workflow file fail on every push?
Because every file in the workflows directory is evaluated when a push arrives, and one that offers no usable event is reported then. Emptying a file does not remove it from consideration, so the failure repeats on every push until the file is deleted or the workflow is disabled.
Is this the same as writing the trigger key with nothing under it?
No, and the annotation tells you which you have. A key present with an empty value is answered with a line, a column and an unexpected empty value, because an empty value is still a value. The message about no event triggers carries no position at all, which is the state where the key itself is absent.
How do I stop a workflow without breaking every build?
Disable the workflow, or delete the file. Both leave nothing that has to be evaluated on each push. Commenting out the events is the one option that keeps the file in play while making it unusable.

Related guides

References

Commenting out the trigger does not disable a workflow. Latchkey runs the ones you keep at $0.0025/min. Start free → 30-day trial · No credit card