# Workflow does not have a workflow_dispatch trigger in this branch

> Workflow does not have a workflow_dispatch trigger is about the ref you picked, not the default branch. Learn which file GitHub reads on dispatch.

Source: https://latchkey.dev/learn/github-actions/github-actions-workflow-dispatch-non-default-branch  
Updated: 2026-09-21

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.

```Dispatch refusal, quoted from age533174851-sudo/auto-platform#248; a tag variant ends "in this tag"
Workflow does not exist or does not have a workflow_dispatch trigger in this branch
```

## 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

1. Ask the API for the workflow file at that exact ref.
2. If the call returns nothing, the file is absent at that ref and no workflow change will help.
3. If it returns the file, read its `on` block at that ref and look for the manual trigger.

```shell
gh api repos/OWNER/REPO/contents/.github/workflows/publish.yml?ref=v1.2.0
```

### Pass 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.

```.github/workflows/publish.yml
- uses: actions/checkout@v5
  with:
    ref: ${{ inputs.release_tag }}
    fetch-depth: 0
```

### Add 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.

```shell
echo "dispatching at ref: $REF"
gh workflow run publish.yml --ref "$REF"
```

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

## Two 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 |

> The last row is the one that generates most of the reports. The workflow is listed, because the default branch has it, and the tag you want to build predates it, so the dispatch is refused at exactly the ref you care about.

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

```.github/workflows/publish.yml
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.

```shell
# 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.0
```

## Why 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.

## FAQ

### Why does the dispatch say the workflow does not have a workflow_dispatch trigger when it clearly does?

Because the file is read at the ref you selected, not at the ref you are working on. The copy of the workflow at that branch or tag either does not exist or does not declare the manual trigger. The message names the ref it read at the end of the sentence.

### Can I run a workflow_dispatch workflow against an old tag?

Only if that tag contains the workflow file with the manual trigger. Since tags should not be rewritten, the usual answer is to dispatch on the default branch and pass the tag as an input, then check that tag out in the job.

### Is this the same as the missing Run workflow button?

No, they are two separate rules. The button is absent when the default branch has no copy of the workflow with the trigger, which is a question of whether the workflow is offered at all. This message appears after the workflow has been offered and a particular ref has been refused.

### Why does the message mention two different problems at once?

Because either one produces the same refusal and GitHub cannot tell you which without more context. The file may be absent at that ref, or present without the trigger. Fetching the file at that ref settles which.

## References

- [GitHub Actions: manually run a workflow](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manually-run-a-workflow)
- [GitHub Actions: events that trigger workflows, workflow_dispatch](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#workflow_dispatch)
- [age533174851-sudo/auto-platform#248: the refusal quoted verbatim](https://github.com/age533174851-sudo/auto-platform/pull/248)
- [GitHub REST API: create a workflow dispatch event](https://docs.github.com/en/rest/actions/workflows#create-a-workflow-dispatch-event)

---

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
