# GitHub Actions workflow not found at ref, and the three messages

> GitHub Actions workflow not found at ref is a remembered phrase rather than a quotable message. Match what you have against the three real ones.

Source: https://latchkey.dev/learn/github-actions/github-actions-requested-workflow-not-found-at-ref  
Updated: 2026-09-21

GitHub Actions workflow not found at ref is how people remember a failure rather than a sentence any of the published components produce, and searching for it is why the trail goes cold. Three different messages are behind that memory, they come from three different mechanisms, and the first useful step is deciding which one you are holding.

## What this error means

Something that references a workflow by name and by ref refuses to resolve it. Depending on which mechanism you used, this arrives as a dispatch that is declined with no run created, as an annotation on a commit carrying a chain of quoted references, or as a request that returns nothing. The common thread is that you can see the file in your editor, on a branch, and the thing in front of you insists it is not there. The three cases need different fixes, and picking the wrong one wastes a morning, so the message text matters more here than usual.

```The two requests that separate the three mechanisms, not a message from a run
# which of the three do you have? the answer is in what exists, not in the wording
gh api repos/OWNER/REPO/contents/.github/workflows/deploy.yml?ref=THE_REF
gh run list --limit 5 --json name,conclusion,databaseId
```

## Common causes

### The ref in the request is not the ref you are looking at

The single most common cause across all three mechanisms. Automation builds the ref from a variable, a default fills in when the variable is empty, and the request goes out against something nobody chose.

### The workflow file was renamed or moved

Every one of the three resolves by path. A rename that looks like tidying leaves every reference pointing at a path that no longer exists, and the references are rarely in the same pull request as the rename.

### The file exists at some refs and not at the one requested

Old tags predate the workflow. Long lived release branches never received it. Both look like the file existing, because it does, on the branch you have open.

### The token cannot see the repository

A private repository is reported as not found rather than as forbidden, on purpose. In our experience this is the cause people reach last and it explains a surprising share of the reports where the path and ref are demonstrably correct.

## How to fix it

### Copy the literal message out of your own log

1. Select the message text in the annotation or the command output, rather than retyping it.
2. If it names a branch or a tag at the end, you have the dispatch refusal.
3. If it contains quoted references joined by arrows, you have the reusable workflow chain.
4. If it is an API response rather than anything in the Actions tab, you have the third case.

### Ask what is actually at the ref

One request settles the argument about whether the file is there, and it removes the temptation to reason from what your editor is showing you.

```shell
gh api repos/OWNER/REPO/contents/.github/workflows/deploy.yml?ref=v1.2.0 \
  --jq '.path // "absent at this ref"'
```

### Print the ref your automation sends

Log the value immediately before the request. A refusal naming a ref you did not expect converts a long investigation into a one line fix, and it is the fastest way to find a default that quietly filled in.

### Update every reference when a workflow file is renamed

1. Search the repository, and any repository that calls it, for the old path.
2. Update reusable workflow references, dispatch scripts and documentation together.
3. Keep the rename and the reference updates in one change so review sees them together.

```shell
grep -rn 'deploy.yml' --include='*.yml' --include='*.sh' .
```

## How to prevent it

- Quote error text from your own log rather than from memory when searching.
- Rename workflow files and their references in a single change.
- Log the ref before any scripted dispatch or call.
- Check token visibility before concluding a file is missing.

## Why the phrase you searched for found nothing

Before anything else, a note on the search that probably brought you here. We looked for the sentence as it is usually written, as an exact phrase, and also for its longest distinctive fragment without any numerals in it, since a numeral in a quoted phrase changes what GitHub search does with it entirely. Neither turned up a report whose body actually contained the words when we fetched the bodies and searched them.

The controls make that meaningful rather than a failed search. A nonsense phrase of the same shape returned nothing, so the quoting was working, and the real messages named below returned results that did contain them when fetched, so the method finds things that are there. The conclusion is narrow and worth stating exactly: this is a phrase people remember and paraphrase, not a string a component emits.

That is not a trivial finding, because it changes what you should do. Searching for a paraphrase returns other people paraphrases, which is how a wrong cause propagates. Copying the literal text out of your own log is the move that ends the confusion.

| What you did | What the message looks like | Whether a run exists |
| --- | --- | --- |
| Dispatched at a ref | a sentence naming the branch or tag | no, the dispatch is declined |
| Called a reusable workflow | a chain of quoted references and arrows | yes, and it ends immediately |
| Asked the API for a workflow | an empty or not found response | not applicable |

> The middle row is the only one that produces a run object, and it produces one with no jobs in it, which is why tooling that looks for a failed job finds nothing to report.

## The three, and how to tell yours apart

The first is a manual dispatch aimed at a ref. The refusal names the branch or tag you selected and offers two possibilities in one sentence, because the file may be absent at that ref or present without the manual trigger. It is decided before any run object exists. Our page on that refusal covers what it means and why merging to the default branch does not fix it.

The second is a reusable workflow call. Here the file that failed to resolve is named inside a chain: the caller, then each reference it followed, joined by arrows, ending in the words about the workflow not being found. The chain is the diagnosis, because the arrow that stops tells you which hop failed. Our page on that annotation walks the chain hop by hop.

The third is an API call that returns nothing useful. Asking for a workflow by file name or id returns not found when the name is wrong, when the file is absent at the default branch, or when the token cannot see the repository. This one has no annotation and no run, only a response, and it is the case most often misread as a workflow problem when it is a naming or permission problem.

```shell
# which of the three are you in?
# 1. dispatch: no run is created
gh workflow run deploy.yml --ref v1.2.0

# 2. reusable call: a run exists with no jobs
gh run list --limit 5 --json databaseId,conclusion,name

# 3. API: read the response, not the Actions tab
gh api repos/OWNER/REPO/actions/workflows/deploy.yml
```

## What every one of the three has in common

All three resolve a workflow against a specific ref, and in all three the ref is the thing most likely to be wrong. A ref is not the branch you have checked out, it is the value in the request, and the two diverge constantly in automation that builds the value from a variable.

A second commonality is that the file is resolved by path. Renaming or moving a workflow file changes the thing every one of the three looks up, and a reference that names the old path fails while the workflow itself is perfectly healthy under its new name. Because renames are usually reviewed as tidying, the references are usually not updated in the same change.

The third is that none of the three is a permissions message, but a permissions problem can produce two of them. A token that cannot see a private repository gets an answer indistinguishable from the file not existing, which is deliberate. If the path and the ref both check out, look at what the token can see before you look at the workflow again.

## Why there is no recorded run on this page

Two of the three failures never create a run, so there is nothing to record. The third creates a run with no jobs, and a capture of that would show an empty run page from our repository, which tells a reader nothing they cannot see on their own.

What this page offers instead of a recording is the negative result: we went looking for the remembered sentence with controls in place and could not establish it, and we can name the three messages that are real and cite where each was observed. That is a more useful artifact than a screenshot, because the reader arriving with a paraphrase needs to be told the paraphrase is the problem.

There is nothing to repair either, in any of the three. A reference that does not resolve is a request no runner ever receives.

## FAQ

### Why can I not find the exact message anywhere?

Because it is a paraphrase rather than a string any published component emits. We searched for the phrase and for its longest fragment without numerals, fetched the candidate bodies and searched those too, with a nonsense control that returned nothing, and could not establish it. Copy the literal text from your own log instead.

### How do I tell a dispatch failure from a reusable workflow failure?

By whether a run exists. A dispatch that cannot resolve is declined and no run is created. A reusable workflow reference that cannot resolve creates a run which then ends immediately with no jobs, and carries an annotation containing a chain of quoted references.

### Does renaming a workflow file break references to it?

Yes, because all three mechanisms resolve the workflow by path. A rename leaves reusable workflow references, dispatch scripts and documentation pointing at a path that no longer exists, and nothing warns you at the time of the rename.

### Could this be a permissions problem?

It can. A token that cannot see a private repository receives a not found answer rather than a forbidden one, by design. If the path and the ref both check out, look at what the token is allowed to see before looking at the workflow again.

## References

- [GitHub Actions: reuse workflows, referencing a reusable workflow](https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows)
- [GitHub REST API: get a workflow](https://docs.github.com/en/rest/actions/workflows#get-a-workflow)
- [age533174851-sudo/auto-platform#248: the dispatch refusal quoted verbatim](https://github.com/age533174851-sudo/auto-platform/pull/248)
- [GitHub Actions: manually run a workflow](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manually-run-a-workflow)

---

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
