Workflow does not have a workflow_dispatch trigger in this branch
Workflow does not have a workflow_dispatch trigger names the branch or tag you selected, and that is the whole diagnosis: the file is read at the ref you dispatched, not at the ref you are working on. Merging to the default branch fixes a different problem, which is why people who have already done that meet this message and conclude the rule is broken.

What this error means
You pick a workflow, choose a ref, press the button or call the API, and the dispatch is refused with a sentence that names the branch or the tag. The workflow is listed, so it clearly exists. It ran last week on the default branch, so the trigger clearly exists too. The thing that changed is the ref, and the part of the message that does the work is its last two words. Dispatching an old release tag is the version of this that catches the most people, because a tag cut before the workflow was written obviously does not contain it, and nothing about the dispatch dialog hints that the tag is where the file will be read from.
Workflow does not exist or does not have a workflow_dispatch trigger in this branchTwo rules, and this page is the second one
Manual runs are governed by two independent rules and most confusion comes from collapsing them into one. The first rule decides whether a workflow can be offered for a manual run at all, and it reads the default branch. A workflow whose manual trigger exists only on a feature branch is not listed, and the Run workflow button is absent. Our page on the missing button covers that rule.
The second rule decides whether a particular dispatch is accepted, and it reads the ref you selected. The workflow file must exist at that ref, and the copy at that ref must declare the manual trigger. This is the rule that produces the message on this page, and you can tell them apart by what you can see: if the workflow is not listed at all you are in the first rule, and if it is listed and the dispatch is refused you are in the second.
Both halves of the second rule matter. A ref that has the file but an older copy of it, written before somebody added the manual trigger, fails the same way as a ref with no file. The message says so out loud by offering both possibilities in one sentence, which is the sort of honesty that reads as vagueness until you know there really are two ways in.
| Where the file is | Offered for a manual run | Dispatch at that ref |
|---|---|---|
| Default branch only | yes | accepted on the default branch |
| Default branch and the feature branch | yes | accepted on either |
| Feature branch only | no | not reachable, nothing is listed |
| Default branch, but not in an old tag | yes | refused at that tag |
Common causes
The selected ref is an old tag cut before the workflow existed
The most frequent shape by a wide margin. The workflow is on the default branch, the tag is older than the workflow, and the dispatch is aimed at the tag because the tag is the code you want.
The file exists at the ref but without the manual trigger
The copy at that ref is an earlier version whose on block has push and pull request but not workflow_dispatch. The message covers this case explicitly, which is why it offers two possibilities rather than one.
The ref was never merged and the dispatch targets it directly
A workflow developed on a feature branch and dispatched at that branch fails the first rule rather than this one, because nothing is listed. If you reached a dispatch at all, the workflow is registered and the ref is the problem.
A script sends a ref that differs from the one intended
Dispatches made from other automation frequently build the ref from a variable. A tag name where a branch was meant, or an empty value falling back to a default, produces a refusal that looks like a permissions problem until you print the ref.
How to fix it
Confirm what is at the ref before editing anything
- Ask the API for the workflow file at that exact ref.
- If the call returns nothing, the file is absent at that ref and no workflow change will help.
- If it returns the file, read its
onblock at that ref and look for the manual trigger.
gh api repos/OWNER/REPO/contents/.github/workflows/publish.yml?ref=v1.2.0Pass the ref as an input instead of dispatching at it
Keep the workflow on the default branch, take the tag or commit as a workflow_dispatch input, and check that out in the job. The workflow you maintain then runs against the code you want, and older refs stop mattering.
- uses: actions/checkout@v5
with:
ref: ${{ inputs.release_tag }}
fetch-depth: 0Add the trigger at the ref if the ref is a live branch
For a long lived release branch rather than a fixed tag, cherry pick the workflow change onto that branch. This is a real fix for a branch you will dispatch repeatedly, and useless for a tag, which should not be rewritten.
Print the ref your automation is sending
Where the dispatch comes from another workflow or a script, log the ref immediately before the call. A refusal that names a ref you did not expect to see turns a confusing permissions hunt into a one line fix.
echo "dispatching at ref: $REF"
gh workflow run publish.yml --ref "$REF"Why an old tag is the usual subject
Release automation is where this lands hardest. A team writes a publish workflow, merges it, and then wants to publish an older release. They select the tag, because that is the code they want built, and the tag was cut before the workflow existed. The refusal is correct and the intent is reasonable, which is an uncomfortable combination.
There is no setting that changes it. The workflow that runs is the one at the ref, because that is what running a workflow at a ref means. Any fix has to either put the file at that ref or stop asking for that ref.
The common resolution is to stop dispatching the tag and start passing it. A workflow on the default branch takes the tag as an input, checks that tag out, and builds it. The workflow definition then comes from the default branch, where it is maintained, and the code comes from the tag, which is what was actually wanted. The alternative, cutting a new tag that contains the workflow, only helps for tags you have not cut yet.
on:
workflow_dispatch:
inputs:
release_tag:
description: The tag to build
required: true
type: string
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
ref: ${{ inputs.release_tag }}Reading the refusal without guessing
The sentence ends by naming what it read, and the last word is either branch or tag depending on what you selected. That is worth noticing, because a team that dispatches by API often passes a ref without thinking about which kind it is, and a tag and a branch of the same name are different refs.
Before changing anything, confirm what is actually at the ref. Asking the API for the file at that ref answers the question in one call and removes the argument about whether the file is there, which is frequently the part people are wrong about. If the file is present, read its trigger block at that ref rather than in your editor, because your editor is showing you the branch you have checked out.
If the dispatch is being made from another workflow or from a script, check that the ref being sent is the one you think. A variable that holds a tag name without the refs prefix and a variable that holds a branch behave differently, and a default that quietly falls back to the checked out ref is easy to miss.
# what is actually at that ref
gh api repos/OWNER/REPO/contents/.github/workflows/publish.yml?ref=v1.2.0 \
--jq '.path'
# dispatch, naming the ref explicitly
gh workflow run publish.yml --ref v1.2.0 -f release_tag=v1.2.0Why there is no recorded run on this page
The refusal happens before a run object exists. GitHub declines the dispatch, so there is no run to record, no job to queue and no log to capture, and a recording made on our side would have to be of a dispatch we deliberately aimed at a ref we had emptied. That is a demonstration of the rule rather than evidence of it.
The message itself is quoted from a report where somebody hit it in their own repository, which is a better source than a fixture, and the behavior it describes is stated in the documentation for manually running a workflow. Those two together are the claim.
There is nothing for a runner to repair either. A dispatch aimed at a ref without the workflow is a request that cannot be honored, and a runner never sees it.
How to prevent it
- Treat the ref in a dispatch as the source of the workflow definition, not only of the code.
- For anything that must run against historic refs, take the ref as an input and check it out.
- Log the ref before any scripted dispatch so a refusal names something you can compare.
- When adding a manual trigger, decide at that moment which refs will ever need it.