# GitHub Actions workflow_dispatch not on default branch

> GitHub Actions workflow_dispatch not on default branch is why the Run workflow button is missing. Learn the rule, then dispatch any ref you like.

Source: https://latchkey.dev/learn/github-actions/github-actions-workflow-dispatch-not-on-default-branch  
Updated: 2026-09-20

GitHub Actions workflow_dispatch not on default branch is a registration rule, not a bug in your file. GitHub reads the set of manually runnable workflows from the default branch only, so a trigger that exists on a feature branch is invisible to both the Run workflow button and the API until the file lands on the default branch.

## What this error means

The workflow file declares the manual trigger, the YAML is valid, and the Actions tab shows the workflow, but the Run workflow button is not there. From the command line it is blunter: the dispatch returns a 404 that names the default branch, which reads like a missing file even though you are looking at the file. The confusing part is that everything else about the workflow works from the feature branch. It runs on push, other workflows can call it, and its checks report normally. Only the manual path is missing.

```gh workflow run, quoted from CivicDataLab/CivicDataSpace-test#30
$ gh workflow run keycloak-tests.yml --repo CivicDataLab/CivicDataSpace-test --ref CI
HTTP 404: workflow keycloak-tests.yml not found on the default branch
```

## Common causes

### The workflow has never been on the default branch

The ordinary case. The file was written on a feature branch, which is where you test workflows, and the manual trigger is the one part of it that cannot be tested from there. Nothing about the file is wrong and nothing changes when you fix the YAML, because the YAML was never the problem.

### The file was renamed or moved on the default branch

Dispatches are addressed by file name or by workflow id, and both are registry entries built from the default branch. Renaming the file mints a new entry and retires the old one, so an automation that names the old file gets the same 404 as a workflow that was never merged.

### The trigger was added in a pull request that is still open

A pull request that adds the trigger does not register it, because nothing has landed on the default branch yet. This is the version that catches teams out during a migration, when the new dispatch-driven workflow is reviewed for a week before anyone can run it.

### The workflow is disabled

A workflow disabled by hand is registered but not runnable. It is easy to tell apart, because the Actions tab marks it and re-enabling it restores the button without touching the file. The automatic sixty-day disable is a different rule and not this one: it applies to scheduled workflows in a public repository, not to dispatch triggers.

## How to fix it

### Land the trigger on the default branch first

1. Merge, or cherry-pick, the workflow file to the default branch with the manual trigger in it.
2. Keep the job body safe to run from the default branch: gate the destructive steps on an input or on the ref.
3. Confirm the button appears, then go back to iterating on your branch.

### Then dispatch against whichever ref you want

Registration and execution are separate, so once the workflow is registered you can run the copy that lives on your branch. The CLI takes the file name and the ref, and the API takes the same pair.

```Terminal (illustrative)
$ gh workflow run deploy.yml --ref feature/deploy -f environment=staging
$ gh run list --workflow deploy.yml --branch feature/deploy
```

### Check what the registry actually holds

When the 404 does not match what you are looking at, list the workflows rather than guessing. The listing is the registry, so it shows you exactly which file names are dispatchable and which workflows are disabled.

```Terminal (illustrative)
$ gh workflow list --all
$ gh api repos/{owner}/{repo}/actions/workflows --jq '.workflows[] | [.path, .state] | @tsv'
```

### Use a different trigger when the default branch is not an option

Some workflows genuinely should not exist on the default branch, for example a one-off migration runner. A `repository_dispatch` event is addressed to the repository rather than to a registered workflow, and a `push` filtered to the branch needs no registration at all. Both cost you the inputs form.

## How to prevent it

- Merge a new manual trigger to the default branch before you write the automation that calls it.
- Treat a workflow file rename as an interface change and update every dispatch that names the old file.
- Gate destructive steps on an input or on `github.ref` so a workflow is safe to land on the default branch early.
- List the workflows from the API in your release runbook rather than trusting the Actions tab to be complete.

## A minimal workflow that produces it

This file is written for this page and has never been run. There is nothing to correct in it. It is a perfectly ordinary manual workflow, and the only thing wrong is where it lives: it was committed on a feature branch and never merged, so the repository has no record of a dispatchable workflow by that name.

```.github/workflows/deploy.yml, on a feature branch (illustrative)
name: deploy
on:
  workflow_dispatch:
    inputs:
      environment:
        description: Target environment
        type: choice
        options: [staging, production]
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: ./deploy.sh "${{ inputs.environment }}"
```

## The rule, in three sentences from the documentation

The events reference carries it as a note on the event itself: "This event will only trigger a workflow run if the workflow file exists on the default branch." The how-to repeats it without hedging: "To trigger the `workflow_dispatch` event, your workflow must be in the default branch."

The same reference then separates the two halves that people conflate: "On the GitHub UI, the \"Run workflow\" button will be present if the workflow file exists on the default branch. Once a workflow has run at least once, you can dispatch it against any branch or tag via the GitHub API or GitHub CLI."

So the default branch answers whether this workflow can be dispatched at all. The ref you pass answers what runs. They are routinely read as one rule, and that is why the usual first attempt at a fix, passing the feature branch as the ref, changes nothing.

## The same file, on the branch that registers it

There is no YAML to correct here, so the corrected sample is the same file with the thing that matters made explicit: it is on the default branch. Land it there first, keeping the job a no-op if you are not ready to run it, and the trigger registers. Read the two samples as a pair: identical content, different branch, and only the second is dispatchable.

Once it is registered you can run it against whichever ref you were trying to test, and the run executes the version of the file on that ref. The events reference records that in the event's own columns: the commit is the "Last commit on the `GITHUB_REF` branch or tag" and the ref is the "Branch or tag that received dispatch".

```Terminal, corrected (illustrative)
$ git switch main
$ git restore --source feature/deploy -- .github/workflows/deploy.yml
$ git commit -m "register the deploy dispatch trigger" .github/workflows/deploy.yml
$ git push

$ gh workflow run deploy.yml --ref feature/deploy -f environment=staging
```

## What the 404 is actually reporting

The message is worth reading precisely, because it is not saying the file is missing from the ref you named. The GitHub CLI builds it in `pkg/cmd/workflow/shared/shared.go`: when a workflow lookup by id or file name returns a 404, it replaces the message with the format string below. The lookup is against the workflow registry, and the registry is built from the default branch.

That also explains a second report that looks unrelated. Renaming a workflow file on the default branch retires the old registry entry, so a dispatch that used the old file name starts returning the same 404 while a workflow of the same display name sits in the Actions tab.

```cli/cli, pkg/cmd/workflow/shared/shared.go
httpErr.Message = fmt.Sprintf("workflow %s not found on the default branch", workflowSelector)
```

| Where the file is | Run workflow button | `gh workflow run --ref X` | Which version runs |
| --- | --- | --- | --- |
| default branch only | yes | yes | the default branch copy |
| default branch and branch X | yes | yes | the copy on X |
| branch X only | no | 404 | nothing runs |
| renamed on the default branch | yes, under the new name | old name 404s | the new file |

> A workflow that is present but rejected on the ref you dispatch is a different failure: [GitHub Actions invalid workflow file on this ref](/learn/github-actions/github-actions-skipping-check-workflow-invalid-on-ref).

## Why there is no recorded run on this page

There is no run to record, which is the whole point of the page: the dispatch is refused before a run exists, so there is no job, no log and nothing for a runner to retry or repair. The error at the top is quoted from CivicDataLab/CivicDataSpace-test#30, where it was reported with the command that produced it and the branch layout that explains it. The workflow above is illustrative.

## FAQ

### Why is the Run workflow button missing in GitHub Actions?

Because the workflow file that declares `workflow_dispatch` is not on the default branch. The events reference is explicit: "On the GitHub UI, the \"Run workflow\" button will be present if the workflow file exists on the default branch." Nothing about the file on your branch changes that.

### Can I run a workflow_dispatch workflow from a non-default branch?

Yes, once it is registered. "Once a workflow has run at least once, you can dispatch it against any branch or tag via the GitHub API or GitHub CLI." The default branch decides whether the workflow can be dispatched; the ref you pass decides which copy of the file runs.

### What does "not found on the default branch" mean from gh workflow run?

It means the workflow registry has no entry for that file name. The GitHub CLI rewrites a 404 from the workflow lookup into that sentence, so it reports on the registry rather than on the ref you passed. CivicDataLab/CivicDataSpace-test#30 is that report.

### Do I have to merge a workflow to test it?

Only the manual trigger. A workflow on a feature branch runs on `push` and on `pull_request` from that branch, and another workflow can call it by ref, so most of it can be tested before it lands. The usual answer is to merge a version with the job gated off.

## References

- [GitHub Actions: events that trigger workflows, workflow_dispatch](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#workflow_dispatch)
- [GitHub Actions: manually running a workflow](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manually-run-a-workflow)
- [CivicDataLab/CivicDataSpace-test#30: workflow_dispatch unavailable off the default branch](https://github.com/CivicDataLab/CivicDataSpace-test/issues/30)
- [cli/cli: pkg/cmd/workflow/shared/shared.go, the 404 message](https://github.com/cli/cli/blob/trunk/pkg/cmd/workflow/shared/shared.go)

---

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
