# GitHub Actions invalid workflow file on this ref

> GitHub Actions invalid workflow file on this ref blocks a required check with nothing red. Find the failing ref, then read the startup annotation.

Source: https://latchkey.dev/learn/github-actions/github-actions-skipping-check-workflow-invalid-on-ref  
Updated: 2026-09-20

GitHub Actions invalid workflow file on this ref is the case where the same file passes on one branch and is rejected on another. A workflow is validated against the ref it runs on, and everything it pulls in resolves at that ref too, so a file you have not touched can become invalid.

## What this error means

A required check never reports. The pull request sits with a check that is expected and never arrives, the merge button stays disabled, and nothing is red, because a run that dies during startup has no failing job to be red. Worse, the same workflow is demonstrably fine: it runs on the default branch and it ran on this branch last week. The only visible trace is a run in the Actions tab with a conclusion that is neither success nor failure, no jobs at all, and a single annotation naming your workflow file and a line number. That annotation is the entire diagnosis, and the pull request page does not show it.

```Invalid workflow file annotation, quoted from mosip/esignet#2551
Invalid workflow file: .github/workflows/push-trigger.yml#L88
The workflow is not valid. .github/workflows/push-trigger.yml (Line: 88, Col: 21):
Invalid input, NODE_VERSION is not defined in the referenced workflow.
.github/workflows/push-trigger.yml (Line: 89, Col: 16):
Invalid input, ZIP_DIR is not defined in the referenced workflow.
```

## Common causes

### A referenced workflow or action changed underneath a moving ref

The commonest version, and the one that makes the failure feel like it came from nowhere. The caller pins to a branch or a floating tag, the upstream file changes its inputs, and every ref carrying the old caller becomes invalid. Nothing in the failing repository was edited.

### The branch carries an older version of the workflow

A long-lived release branch or a stale pull request branch keeps the file as it was, including references since corrected on the default branch. The fix landed only where it was applied, so the default branch is healthy and every other ref is not.

### The workflow was edited on one ref and not merged

A change made on a branch, or a pull request that fixes the workflow and is itself blocked by it, leaves the two refs disagreeing. It feels circular: the check that would let you merge the fix is the one the fix repairs.

### The check comes from an event that does not count

A run triggered by an event outside the eligible list reports checks that never satisfy a required status check, however green they are. Different cause, same symptom: a merge box waiting forever.

## How to fix it

### Find the run that has no jobs

1. Open the Actions tab and filter by the branch, not by the pull request.
2. Look for a run with no jobs listed and a conclusion that is not success or failure.
3. Open it and read the annotation: it names the file, the line and the column on the ref that failed.

```Terminal (illustrative)
$ gh run list --branch release-2.0.x --limit 10
$ gh run view <run-id>
```

### Compare the file across the two refs

When one branch validates and another does not, the difference is in the file or in what the file resolves to. A diff answers the first in one command; if the files are identical, a reference is floating.

```Terminal (illustrative)
$ git diff origin/main..origin/release-2.0.x -- .github/workflows/
```

### Pin what you call, then update it deliberately

A floating reference makes every ref in the repository depend on the current state of another one. Pinning converts that into a change you approve, and an updater turns it into a pull request. The alternative is a branch that breaks on somebody else's schedule.

### Fix every ref that carries the broken caller

Landing the fix on the default branch hides the symptom rather than ending it, because each branch validates its own copy. List the branches that still carry the old reference and cherry-pick the correction onto each, or accept that they cannot build.

```Terminal (illustrative)
$ git branch -r --list 'origin/release-*'
$ git grep -l 'npm-build.yml@develop' $(git branch -r --list 'origin/release-*') -- .github/workflows
```

## How to prevent it

- Pin called workflows and third-party actions, so no ref of yours depends on the current state of someone else's.
- Validate workflow files in a pre-commit hook with a linter that parses the expression grammar, not just the YAML.
- Treat a required check as a contract and avoid requiring one that a workflow can fail to produce.
- Sweep long-lived release branches when a workflow fix lands, because each one validates its own copy.

## A minimal workflow that produces it

This file is written for this page and has never been run. Read on its own it is unremarkable: it calls a shared workflow at a moving ref and passes two inputs. It is valid the day it is written and stays valid until somebody in the other repository removes one of those inputs, at which point every ref still carrying this version becomes invalid and the updated refs do not.

```.github/workflows/ci.yml (illustrative)
name: ci
on:
  push:
  pull_request:

jobs:
  build:
    uses: octo-org/shared/.github/workflows/npm-build.yml@develop
    with:
      service: web
      node_version: "22"
      zip_dir: dist
    secrets: inherit
```

## Validation happens per ref, not per repository

A workflow is evaluated as it exists on the ref that triggered it, together with everything it references resolved at that moment. That is usually invisible, because most repositories keep one version of a workflow. It becomes visible when two refs carry different versions of the same file, or the same version of a file whose dependencies have moved underneath it.

mosip/esignet#2551 is that story with the receipts. A caller still passed two inputs to a shared workflow at a moving ref; the shared workflow had dropped both weeks earlier. One release branch had already pushed and had run nothing since, and the issue marks two more branches broken on their next push, with only the branch that took the fix healthy. Nothing had changed in the repository that broke.

The documentation explains why the failure repeats rather than happening once: "Ensure that you only commit valid workflow files to your repository. If `.github/workflows` contains an invalid workflow file, GitHub Actions generates a failed workflow run for every new commit."

## The corrected file, pinned rather than floating

The correction is not in the inputs, it is in the reference. Pinning the call to a tag or a commit means the shared workflow it resolves to is the one this branch was tested against, so a change upstream arrives as a pull request rather than as a branch that stops validating. Read the two samples as a pair: the first is valid until somebody else edits a file in another repository, the second until you decide otherwise.

Where the inputs really have moved, the second half of the fix is to update the caller on every ref that still uses the old ones. Fixing the default branch makes the symptom disappear from view while every long-lived release branch stays broken.

```.github/workflows/ci.yml, corrected (illustrative)
jobs:
  build:
    uses: octo-org/shared/.github/workflows/npm-build.yml@v4.2.0
    with:
      service: web
    secrets: inherit
```

## Why the pull request shows nothing

A run that fails during startup never creates a check run for any job, so branch protection has nothing to see. A check run that is `expected` is "waiting for a status to be reported", and the check suite carries `startup_failure`: "The check suite failed during startup. This status is not applicable to check runs."

The troubleshooting guide describes the same dead end from the merge box: when a required job never produces a check, "the pull request is blocked with \"Waiting for status to be reported.\"" It also lists the events whose checks are eligible at all, the other reason a check you can see in the Actions tab does not count: `push`, `pull_request`, `pull_request_review`, `pull_request_target`, `deployment` and `deployment_status`.

From the command line the state is easier to see. The GitHub CLI prints a run with no jobs and a failing conclusion as "This run likely failed because of a workflow file issue.", the nudge toward the annotation that the pull request page never gives you.

| What you see | What it means | Where the detail is |
| --- | --- | --- |
| a run with no jobs | startup, not a job, failed | the run annotation |
| conclusion `startup_failure` | the check suite never started | the run annotation |
| a required check `expected` | no check run was ever created | the Actions tab, not the pull request |
| valid on one branch only | a reference resolved differently there | the caller on that ref |
| failing on every new commit | the file is invalid, not the commit | the workflow file on that ref |

> When the reference itself cannot be resolved rather than its inputs being wrong, the message is different: [GitHub Actions reusable workflow was not found](/learn/github-actions/gha-reusable-workflow-was-not-found).

## Why there is no recorded run on this page

There is a run and it has nothing in it. Validation failed before any job was created, so there is no log to capture and no runner was ever involved. The annotation at the top is quoted from mosip/esignet#2551, which names the branch, the two lines and the run it came from.

## FAQ

### Why is my GitHub Actions workflow invalid on one branch only?

Because each ref is validated on its own: the file on that branch, and everything it references resolved at that ref, has to be valid together. A caller pinned to a moving upstream ref can become invalid without anybody editing it, which is what mosip/esignet#2551 reported.

### What does startup_failure mean in GitHub Actions?

That the check suite failed before any job was created. The status reference defines it as "The check suite failed during startup. This status is not applicable to check runs." There are no jobs and no logs, only a run-level annotation naming the file and the line.

### Why is a required check stuck waiting for a status to be reported?

Because no check run was ever created for it. A check run in the `expected` state is "waiting for a status to be reported", and a workflow that dies at startup produces none, so branch protection waits forever.

### How do I see the error when a run has no jobs?

Open the run itself rather than the pull request and read the annotation, or use the CLI, which prints "This run likely failed because of a workflow file issue." and the run URL. The annotation carries the file, the line and the column.

## References

- [GitHub: pull request status checks, statuses and conclusions](https://docs.github.com/en/pull-requests/reference/status-checks)
- [GitHub: troubleshooting required status checks](https://docs.github.com/en/pull-requests/how-tos/merge-and-close-pull-requests/troubleshooting-required-status-checks)
- [mosip/esignet#2551: a caller invalid on one release branch only](https://github.com/mosip/esignet/issues/2551)
- [GitHub Actions: using workflow run logs](https://docs.github.com/en/actions/how-tos/monitor-workflows/use-workflow-run-logs)

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
