You maintain a shared GitHub Actions workflow. You need to rename an input, drop a step, or change a required secret. Which repos across your org call it? Here’s why the answer is harder than it should be.
You maintain a reusable GitHub Actions workflow. Maybe it’s deploy-to-eks.yml in your platform team’s .github repo, or a ci-lint-test.yml that standardises how every service in the org runs its CI pipeline. It started as a way to stop every team from copy-pasting the same 80-line workflow file. It worked. Twenty repos adopted it. Then forty. Then you lost count.
Now you need to change it. Maybe you’re replacing a deprecated action (actions/setup-node@v4 is being forced to Node 24 and you need to bump to v6). Maybe you’re renaming an input from node-version to runtime-version because the workflow now supports Bun and Deno too. Maybe you’re removing a step that uploaded artifacts to a bucket that no longer exists.
The question is the same one that comes up for every shared infrastructure artifact: which repos across our org call this workflow, and at which ref?
The scenario
Here’s what a typical reusable workflow consumption looks like. Your platform team publishes a workflow in a central repository, and other repos call it:
# In some-service/.github/workflows/ci.yml
jobs:
test:
uses: my-org/platform-workflows/.github/workflows/[email protected]
with:
node-version: "20"
secrets: inherit
Some repos pin to a tag. Some pin to a branch like main. Some pin to a full commit SHA for supply-chain security. Some don’t pin at all and reference @main, which means they’re calling whatever version is on HEAD right now.
You need to find all of them, understand which version each one uses, and figure out which will break when you rename that input. Right now, most teams do this by searching in GitHub, asking in Slack, or just pushing the change and waiting for workflows to fail. None of these are a real answer.
What existing tools give you (and where they stop)
GitHub code search
GitHub’s code search is the most natural place to look. You can search across all repos in an org:
org:my-org "uses: my-org/platform-workflows/.github/workflows/ci-lint-test.yml"
This works for finding direct string matches. For a one-off audit, it’s often good enough. But it has real limitations:
- It doesn’t extract the ref. You can see that a repo calls the workflow, but the
@v2.1.0or@mainpart requires manually opening each result and reading the YAML. - Results are point-in-time. If you’re planning a breaking change, you need to know who’s calling the workflow now — not when the search index was last updated.
- The search index can lag. GitHub’s code search is eventually consistent. Repos that were recently updated might not show current results immediately.
- It doesn’t handle indirection. If a repo has a “wrapper” workflow that calls your reusable workflow, and other workflows call the wrapper, GitHub code search only finds the direct callers.
Dependabot
Dependabot supports GitHub Actions as a first-class ecosystem. In a repo’s dependabot.yml, you can configure it to watch for action version updates:
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
Dependabot will then open pull requests when actions referenced in the repo’s workflows have new versions available. This includes reusable workflows referenced with uses:.
The same pattern as Renovate for Terraform: Dependabot implicitly knows who consumes what, because it’s configured per-repo. But it doesn’t expose this as a queryable view. You can’t ask Dependabot “which repos in my org call platform-workflows/ci-lint-test.yml?” It’s a version updater, not a dependency mapper.
There’s an additional limitation: Dependabot only monitors repos where it’s been explicitly enabled. If a team hasn’t added github-actions to their dependabot.yml — or doesn’t have a dependabot.yml at all — their workflow dependencies are invisible.
Renovate
Renovate also supports GitHub Actions workflows, including reusable workflow references. It can detect uses: directives in workflow files and open PRs when new versions are available.
Renovate has a slight edge over Dependabot here: it can be configured org-wide via a shared preset, so you don’t need every repo to opt in individually. But the core limitation is identical — it’s a dependency updater, not a dependency mapper. It reacts when a new version exists. It doesn’t give you the blast radius before you publish the change.
GitHub API
The GitHub API lets you list workflows and workflow runs across repositories. You can enumerate all repos in an org, fetch their workflow files, and parse the YAML yourself.
This is essentially building a custom scanner. The API gives you the raw data, but assembling it into an answer requires:
- Paginating through every repo in the org
- Fetching the contents of every
.github/workflows/*.ymlfile - Parsing YAML to extract
uses:directives - Filtering for your specific reusable workflow
- Extracting the ref from each reference
- Handling rate limits (the GitHub API is aggressive about rate limiting for large orgs)
Teams that go this route end up building a script, running it periodically, and storing the results somewhere. It works. It’s also a project you now have to maintain, and the results are stale between runs.
Grep
Clone everything, grep for the workflow reference:
grep -r "platform-workflows/.github/workflows/ci-lint-test.yml" --include="*.yml" ~/repos/
Same trade-offs as always: works once, stale immediately, doesn’t extract versions, doesn’t catch transitive calls, and at 100+ repos it’s slow enough that nobody does it proactively.
Why this is harder than it looks
Reusable workflow references look simpler than Terraform module sources or Docker image references — there’s really only one syntax. But the complexity is in what surrounds that syntax.
Ref types vary widely. The @ref portion of a workflow call can be a tag, a branch, or a full SHA:
# Tag — the common case
uses: my-org/platform-workflows/.github/workflows/[email protected]
# Branch — consuming HEAD, inherently unstable
uses: my-org/platform-workflows/.github/workflows/deploy.yml@main
# SHA — supply-chain hardened, but opaque
uses: my-org/platform-workflows/.github/workflows/deploy.yml@a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
A tag tells you the version. A branch tells you almost nothing — the consumer is on whatever commit main happened to point to when they last ran. A SHA is precise but requires resolving the commit to a tag or branch to understand which version the consumer is actually using. Answering “who’s still on v1.x?” requires understanding all three forms.
Reusable workflows vs. composite actions. GitHub Actions has two mechanisms for sharing CI logic: reusable workflows (called with uses: at the job level) and composite actions (called with uses: at the step level). They look similar in YAML but behave differently:
# Reusable workflow — job level
jobs:
deploy:
uses: my-org/platform-workflows/.github/workflows/deploy.yml@v2
# Composite action — step level
jobs:
build:
steps:
- uses: my-org/platform-actions/setup-toolchain@v3
Both are dependencies on shared code in other repos. Both break when the interface changes. A consumer tracking system needs to find both, because platform teams often maintain a mix of reusable workflows and composite actions — and the same “who’s using this?” question applies to both.
The .github repository. GitHub has a special convention: a repo named .github in an org can contain workflow templates that appear in every repo’s “Actions” tab. If your platform team publishes starter workflows through the .github repo, those templates get copied (not referenced) into consuming repos. Once copied, they’re no longer linked to the source — there’s no uses: directive pointing back, just a disconnected copy that may or may not have been modified since.
This means some of your “consumers” are invisible. They took the template, modified it, and now maintain their own fork. You can’t find them by searching for uses: directives because there aren’t any.
Inputs and secrets are the breaking surface. Unlike Terraform modules, where the interface is defined by variables and outputs with explicit types, reusable workflow inputs are loosely typed. A workflow declares its inputs:
on:
workflow_call:
inputs:
node-version:
required: true
type: string
secrets:
DEPLOY_KEY:
required: true
If you rename node-version to runtime-version, every caller that passes node-version will silently fail — the input just won’t be received, and the workflow will use its default or break downstream. There’s no compile-time error. The failure shows up at runtime, in the middle of a CI run, and the error message often doesn’t clearly point to the renamed input.
Workflow call chains. Reusable workflows can call other reusable workflows, up to ten levels deep and fifty total workflow calls per run (GitHub increased these limits in late 2025). If workflow A calls workflow B, which calls workflow C, and you change C’s interface, both A and B are affected — but only B directly references C. Finding A requires resolving the full call chain.
What the full answer requires
To reliably answer “who consumes this reusable workflow,” you need a system that:
- Scans every repo in the org, parsing all
.github/workflows/*.ymlfiles — not just repos that have opted into Dependabot or Renovate - Extracts
uses:directives at both the job level (reusable workflows) and step level (composite actions), because platform teams typically maintain both - Resolves ref types — tags, branches, and SHAs — to a normalised version, so you can answer “who’s still on v1.x?” regardless of how the ref was specified
- Follows workflow call chains so you see transitive consumers, not just direct callers
- Keeps the graph current through regular rescans, not one-off audits
- Makes the result queryable: “show me every consumer of
ci-lint-test.yml, grouped by ref, with the team that owns each calling repo”
This is one of the specific problems Riftmap is built to solve. It scans a GitHub (or GitLab) org, parses every workflow file in every repo, extracts all uses: directives — including reusable workflow calls, composite action references, and standard action pins — and builds a cross-repo dependency graph that you can query by artifact.
The result: before you rename that input or remove that step, you open the graph, click the workflow, and see exactly which repos call it and which ref each one pins to. You know who’ll break. You know who to notify. You know who’s pinned to main and will see the change immediately versus who’s on a tagged release and has time to migrate.
No org-wide grep. No stale GitHub code search. No “let’s push it and see whose CI goes red.”
This is the third post in the Find Every Consumer series. Previous posts cover Docker base images and Terraform modules. Next up: Helm charts.
If this is a problem your platform team deals with, I’d be interested to hear how you’re solving it today. You can find more at riftmap.dev or reach me at [email protected].