GitHub Actions reusable workflow was not found
GitHub Actions reusable workflow was not found means the reference was in an accepted shape and still resolved to nothing. The message is more helpful than it first looks, because it prints the whole chain it walked, and the arrow that stops is the one to read.

What this error means
The run is created and then ends immediately, with no jobs and no logs. In the Actions tab it carries an annotation titled with the caller file and a line number, and the body is a chain of quoted references joined by arrows, ending in a colon and the words that give this page its name. Because nothing ran, there is no step to open and any tooling looking for a failed job finds none. The chain is the diagnosis: each arrow is one hop the resolver made, so the reference on the last line is the one that could not be loaded. On a nested call that line is often a workflow you did not write the reference for.
error parsing called workflow
".github/workflows/4_builderpackage_dashboard.yml"
-> "wazuh/wazuh-security-dashboards-plugin/.github/workflows/4_builderpackage_security_plugin.yml@v4.14.8-rc1"
(source tag with sha:e9267cedfeaf707032ac9454ce494a32745585a3)
--> "./.github/workflows/4_builderprecompiled_base-dev-environment.yml"
: workflow was not found.
See https://docs.github.com/actions/learn-github-actions/reusing-workflows#access-to-reusable-workflowsA minimal workflow that produces it
This file is written for this page and has never been run. The reference is in the accepted shape, so nothing about its form is rejected. It fails one step later, when the resolver tries to load that file at that ref and finds no file, no ref, or a file it is not allowed to read.
name: release
on:
push:
tags: ["v*"]
jobs:
package:
uses: octo-org/shared/.github/workflows/package.yml@v2
with:
target: linux
secrets: inheritCommon causes
The path or the file name is wrong
The ordinary case. A called workflow must sit directly in the workflows directory, so a file moved into a subdirectory stops resolving, and so does an extension that is .yaml on one side and .yml on the other. The message names the file it looked for.
The ref does not exist on the target repository
Refs are resolved on the repository that owns the called workflow, not on yours. A release that tags several repositories at once is the usual source: the tag exists where you are standing but not where you are calling.
The called workflow does not declare workflow_call
The file is found and still refused, because nothing marks it as callable. This one has its own wording, about a missing trigger rather than a missing workflow, and it is common when a manually run workflow gets reused.
The caller cannot read the target repository
A private or internal repository has to allow its workflows to be used elsewhere, and the setting lives on the target rather than on the caller. Nothing about the reference looks wrong, which is why the error links the access documentation.
The failing reference is one level down
Resolution recurses, so a workflow you call may call others, and a relative reference inside it resolves against its own repository and commit. The chain is the only place that distinction is visible.
How to fix it
Read the chain from the bottom up
- Find the entry after the last arrow: that is the reference that failed.
- Note which repository it belongs to, which is the repository of the entry above it for a relative reference.
- Check the path, then the ref, then the access, in that order, against that repository.
Confirm the file and the ref from the API
One call tells you whether the file exists at that ref on that repository, answering the path and ref questions together.
$ gh api repos/octo-org/shared/contents/.github/workflows/package.yml?ref=v2 --jq .name
$ gh api repos/octo-org/shared/git/ref/tags/v2 --jq .objectMake the called workflow callable
A workflow becomes reusable by declaring the trigger, and it can carry several at once. Keeping the manual trigger beside the callable one is the usual arrangement, so the same file stays runnable on its own.
on:
workflow_dispatch:
workflow_call:
inputs:
target:
type: string
required: trueReplace fragile relative references in shared workflows
A relative reference inside a workflow other repositories call resolves against the commit that workflow was loaded from, which is stable in principle and has been reported to behave differently across event paths and through annotated tags. In a shared library, write the full reference pinned to a commit.
base:
uses: octo-org/shared/.github/workflows/base.yml@8f4b7c2e0d1a9f3b6c5e2d4a7b8c9e0f1a2b3c4dFour checks, in order, before any job exists
The resolver runs before the run has jobs, which is why a failure here produces a run with none. It loads the caller, walks every job that calls a workflow, resolves the reference, parses the file it found, and recurses into anything that file calls in turn. The loader in actions/runner does exactly that, with a depth limit and the caller file and position prefixed to every error.
Four things have to be true and the message says which one was not. The path has to exist under the workflows directory of the target repository, because "Subdirectories of the workflows directory are not supported". The ref has to exist there. The caller has to be allowed to read it. And the file has to be callable: "For a workflow to be reusable, the values for on must include workflow_call."
That last one has its own wording, worth recognizing because it is the one case where the file was found. crossplane-contrib/provider-workflows#28 quotes it, below: a shared workflow that declared only a manual trigger, called from a template repository. The runner carries the matching check in its converter, which errors when the referenced file has no such key.
failed to parse workflow: error parsing called workflow "" ->
"crossplane-contrib/provider-workflows/.github/workflows/tag.yml@main":
workflow is not reusable as it is missing a `on.workflow_call` triggerThe corrected pair
The caller above usually needs nothing changed, because the defect is on the other side. The corrected sample is the called workflow, which gains the trigger that makes it callable and the input the caller passes. Read them as a pair: with the first file alone the run dies at startup; with this file present at the ref the caller names, the same caller resolves and runs.
If the called file already has that trigger, the remaining candidates are location and access: check the ref exists on the target repository rather than on yours, and check the Actions access setting there if it is private.
# octo-org/shared, .github/workflows/package.yml at tag v2
name: package
on:
workflow_call:
inputs:
target:
type: string
required: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- run: ./package.sh "${{ inputs.target }}"Reading the chain, hop by hop
The chain is the most useful part of the message and the part most often skipped. The example at the top has three entries and two arrows. The caller resolved. The cross-repository call resolved, and the resolver prints the commit the tag pointed at, so the ref was real. The third entry, a relative reference inside that second workflow, is the one that failed, and nobody in the calling repository wrote it.
That is why a reference you have checked three times keeps failing: the wrong one is a level down. wazuh/wazuh-dashboard-plugins#9158 is that report, where four release runs ended with conclusion startup_failure, zero jobs and zero logs, because a nested relative reference could not be resolved through an annotated tag. It also notes the second-order damage: the run never gets far enough to evaluate its own name, so tooling searching by name does not find it.
| What failed | Where to look | What the chain shows |
|---|---|---|
| the path | the target repository | the last entry names a file that is not there |
| the ref | tags and branches on the target | no resolved SHA printed beside the entry |
| access | Actions settings on the target | the entry looks correct and still fails |
no workflow_call | the on block of the called file | a different message about the trigger |
| a nested call | the called workflow, not yours | the failing entry is after the second arrow |
Why there is no recorded run on this page
There is a run, and that is what makes it unrecordable here: no jobs, no steps and no logs, because it ended during resolution. Nothing about it is transient and no runner was ever assigned, so there is nothing to repair. The annotation at the top is quoted from wazuh/wazuh-dashboard-plugins#9158, which links the four affected runs.
How to prevent it
- Keep every callable workflow directly in the workflows directory, because subdirectories are not supported.
- Push tags to the repository that owns the called workflow before the caller is tagged.
- Declare both triggers on a shared workflow so it stays runnable on its own and callable from elsewhere.
- Use full pinned references inside shared workflow libraries, and relative ones only within one repository.
Frequently asked questions
What does "reusable workflow was not found" mean in GitHub Actions?
Why does my reusable workflow fail with no jobs and no logs?
startup_failure, zero jobs and nothing to open. wazuh/wazuh-dashboard-plugins#9158 reports exactly that across four release runs.Why does it say the workflow is not reusable?
on: workflow_call. The documentation states the requirement plainly: "For a workflow to be reusable, the values for on must include workflow_call." crossplane-contrib/provider-workflows#28 is the report, filed against a shared workflow that carried only a manual trigger.Can a reusable workflow call another reusable workflow?
Related guides
References
- GitHub Actions: reusing workflows, creating and calling a reusable workflow
- wazuh/wazuh-dashboard-plugins#9158: startup_failure from a nested relative reference
- crossplane-contrib/provider-workflows#28: missing on.workflow_call trigger
- actions/runner: the reusable workflows loader and its resolution order
- GitHub Actions documentation