Reusable Workflows(再利用可能なワークフロー)は、on: workflow_call を持つワークフローファイルを、別のワークフローのジョブから uses で呼び出す仕組みです。複数のリポジトリにコピーしているCIやデプロイの手順を1本にまとめ、参照で使い回せます。この記事ではGitHub公式の手順書とリファレンスをもとに、定義と呼び出し、inputs・secrets・outputsの受け渡し、上限、2026年に増えた構文と運用上の注意を2026年10月31日時点で整理します。GitHub Actions自体の基本はGitHub Actionsとは?できること・使い方と最初のワークフローで扱っています。
まとめ:Reusable Workflowsの要点
- 呼び出される側は
onにworkflow_callを含め、.github/workflows直下に置きます。サブディレクトリのファイルは呼び出せません。 - 呼び出しはジョブ単位です。
jobs.<job_id>.usesに書き、そのジョブにruns-on・steps・env・environmentは書けません。 - inputsの型は boolean・number・string の3種類で、
typeは必須です。 - 環境シークレットも呼び出し元が渡す必要があります。渡し忘れると呼び出し先では空文字になり、エラーは出ません。2026年10月7日に公式ドキュメントがこの挙動を明記しました。
- github.comの上限は10段・50本です。GitHub Enterprise Server(GHES)は4段・20本のままです。
- 共通リポジトリのスクリプトは
job.workflow_repositoryとjob.workflow_shaでチェックアウトできます(2026年9月3日追加・GHES不可)。 - 2026年10月28日に一般提供されたロックファイルは、Reusable Workflowの呼び出しを固定しません。呼び出し先はフルのコミットSHAで参照します。
- actionlint v1.7.12(10月31日時点の最新)は
$/・cache-mode・job.workflow_*をエラーにします。GitHub上ではどれも有効です。
workflow_callによる定義と呼び出しの最小構成
公式は、使う側を「caller workflow(呼び出し元)」、使われる側を「called workflow(呼び出し先)」と呼びます。呼び出し先は通常のワークフローと同じYAMLで、違いは on に workflow_call が入っていることだけです。呼び出すと、呼び出し先の全ジョブが呼び出し元の一部として実行され、ジョブとステップはログに個別に表示されます。
呼び出される側:on.workflow_callと置き場所
# .github/workflows/reusable-test.yml(呼び出される側)
name: reusable-test
on:
workflow_call:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: npm ci && npm test
置き場所は .github/workflows 直下に限られます。公式は「Subdirectories of the workflows directory are not supported」と明記しており、.github/workflows/shared/ のような分類はできません。共通ワークフローが増える場合は、reusable- や deploy- のようなファイル名の接頭辞で分類します。
呼び出す側:usesの3つの参照形式
# .github/workflows/ci.yml(呼び出す側)
name: ci
on:
pull_request:
jobs:
test:
uses: ./.github/workflows/reusable-test.yml
| 書式 | 参照先 | @ref | GHES |
|---|---|---|---|
$/.github/workflows/x.yml |
同じリポジトリ | 付けられない | 使用不可 |
./.github/workflows/x.yml |
同じリポジトリ | 付けない | 使用可 |
owner/repo/.github/workflows/x.yml@ref |
別リポジトリも可 | 必須 | 使用可 |
公式の参照構文では、同じリポジトリの呼び出しは $/ が推奨で、$/ と ./ のどちらも呼び出し元と同じコミットの呼び出し先が使われます。@ref にはコミットSHA、リリースタグ、ブランチ名を書けます。同名のタグとブランチがあればタグが優先され、公式はコミットSHAを最も安全な選択肢としています。refs/heads/ のような接頭辞は付けられず、uses の値に式やコンテキストも使えません。呼び出し先を変数で切り替えたい場合は、ジョブを分けて if で選びます。
呼び出しジョブに書けるキー
呼び出しジョブに書けるのは name、uses、with、secrets、strategy、needs、if、concurrency、permissions と、github.com限定の cache-mode だけです。runs-on、steps、env、environment、container、services、timeout-minutes は書けないため、ランナーやタイムアウトは呼び出し先のジョブで決めます。呼び出し元から変えたい値は、inputsとして渡す設計にします。
inputs・secrets・outputsの受け渡し
呼び出し先は on.workflow_call の下で、受け取る値(inputs)、受け取るシークレット(secrets)、返す値(outputs)を宣言します。宣言にないinputや、名前を個別指定した未宣言のsecretを渡すとエラーになります。例外は secrets: inherit で、この場合は未宣言のシークレットも参照できます。
inputsの型と既定値
type は必須で、値は boolean、number、string のいずれかです。workflow_dispatchにある choice や environment 型は使えません。default を省略した場合の既定値は、booleanが false、numberが 0、stringが空文字です。配列やオブジェクトはJSON文字列にしてstringで渡し、呼び出し先で fromJSON() を使って展開します。
secretsの個別指定とsecrets: inherit
シークレットは on.workflow_call.secrets に名前と required を宣言し、呼び出し元の jobs.<job_id>.secrets で1つずつ渡します。secrets: inherit と書けば、呼び出し元が使えるシークレットをまとめて渡せます。公式の記述では、inherit を使えるのは同じOrganizationか同じEnterpriseの中のワークフローを呼ぶ場合です。それ以外の公開ワークフローには1つずつ明示して渡します。
シークレットが届くのは直接呼び出した先までです。A→B→Cと入れ子にした場合、AがBへ inherit で渡しても、BがCへ改めて渡さなければCでは使えません。Bが1つだけをCへ渡せば、Cが受け取るのはその1つだけです。
環境シークレットの渡し方(2026年10月の公式ドキュメント更新)
本番の認証情報をEnvironmentのシークレットに置いている場合の扱いは、2026年10月7日の公式ドキュメント更新で次のように明記されました。以前の版から読み取りにくかった点で、「環境シークレットは呼び出し元から渡せない」という説明は現行の記述と合いません。
- 呼び出しジョブには
environmentを書けないため、呼び出し先のジョブにenvironmentを書きます。 - それでも呼び出し元はシークレットを渡す必要があります。
secrets: inheritか名前指定で渡し、名前指定はそのシークレットが環境にしか無くても書けます。 - 環境シークレットとリポジトリ・Organizationのシークレットが同名なら、
environmentを書いたジョブには環境シークレットの値が入ります。 - 呼び出し元が渡し忘れると、呼び出し先では空文字になり、実行はエラーになりません。
最後の挙動が事故につながります。空のトークンでデプロイコマンドが走り、認証エラーがツール側の分かりにくいメッセージで出るためです。呼び出し先の on.workflow_call.secrets で required: true にすれば、渡し忘れた時点で実行が失敗します。ただし公式によると、これは渡したかどうかだけを見る設定で、値が空でないかは確かめません。
# 共通リポジトリ octo-org/shared-workflows の .github/workflows/deploy-reusable.yml
name: deploy-reusable
on:
workflow_call:
inputs:
target:
type: string
required: true
secrets:
DEPLOY_TOKEN:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.target }}
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
repository: ${{ job.workflow_repository }}
ref: ${{ job.workflow_sha }}
path: shared
- run: ./shared/scripts/deploy.sh "$TARGET"
env:
TARGET: ${{ inputs.target }}
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
# 利用側リポジトリの .github/workflows/release.yml
name: release
on:
push:
tags: ["v*"]
jobs:
deploy:
uses: octo-org/shared-workflows/.github/workflows/deploy-reusable.yml@0123456789abcdef0123456789abcdef01234567
with:
target: production
secrets:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
DEPLOY_TOKEN が production 環境にしか無くても、呼び出し元は名前指定で渡せます。@ の後ろの40桁は例示用のコミットSHAで、実際の共通リポジトリのSHAに置き換えます。job.workflow_repository を使うチェックアウトは次の章で説明します。
outputsの返却経路とmatrix時の値
呼び出し先の値を呼び出し元で使うには、ステップ出力をジョブ出力に、ジョブ出力を on.workflow_call.outputs.<name>.value に対応付けます。ステップ出力を直接書くことはできません。呼び出し元では通常のジョブと同じく needs.<job_id>.outputs.<name> で読みます。
# .github/workflows/reusable-test.yml(inputs・secrets・outputs付き)
name: reusable-test
on:
workflow_call:
inputs:
node-version:
type: string
default: "24"
run-lint:
type: boolean
default: true
secrets:
npm-token:
required: false
outputs:
coverage:
description: "テストのカバレッジ率"
value: ${{ jobs.test.outputs.coverage }}
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
coverage: ${{ steps.cov.outputs.percent }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: ${{ inputs.node-version }}
registry-url: "https://registry.npmjs.org"
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.npm-token }}
- if: ${{ inputs.run-lint }}
run: npm run lint
- id: cov
run: |
percent="$(node scripts/coverage.js)"
echo "percent=$percent" >> "$GITHUB_OUTPUT"
# .github/workflows/ci.yml(matrixで呼び出して結果を読む)
name: ci
on:
pull_request:
jobs:
test:
strategy:
matrix:
node: ["22", "24"]
uses: ./.github/workflows/reusable-test.yml
with:
node-version: ${{ matrix.node }}
secrets:
npm-token: ${{ secrets.NPM_TOKEN }}
report:
needs: test
runs-on: ubuntu-latest
steps:
- run: echo "coverage=${{ needs.test.outputs.coverage }}"
matrixで呼び出した場合、outputsには実際に値を設定した実行のうち最後に成功したものの値が入ります。最後に成功した実行が空文字を設定したときは、それ以前に空でない値を設定して成功した実行のうち、最後に完了したものの値になります。各実行の結果を個別に集めたいときは、outputsではなくアーティファクトで受け渡します。
この2ファイルは actionlint v1.7.12 の静的検証でエラーが出ません。実行には、呼び出し元リポジトリに package.json と整合する package-lock.json または npm-shrinkwrap.json、package.json の lint スクリプトと、カバレッジ率を数値で標準出力に出す scripts/coverage.js が必要です。percent を代入文に分けているのは、echo の中でコマンド置換すると、スクリプトが失敗してもステップが成功扱いになるためです。
別リポジトリ・Organizationで共有する設定
呼び出し先を置くリポジトリの公開範囲によって、呼び出せる相手が決まります。github.comの個人アカウントとOrganizationでは次のとおりです。
| 呼び出し元リポジトリ | 呼び出せる置き場所 |
|---|---|
| private | private・public |
| public | public |
GitHub Enterprise CloudとGHESではinternalリポジトリが加わり、privateからはinternal、internalからはinternalとpublicを呼び出せます。privateやinternalに置いた呼び出し先は、そのリポジトリの Settings → Actions → General 最下部の「Access」で、呼び出し元からのアクセスを明示的に許可しないと使えません。呼び出し元の側では、Actionsの許可設定が外部のアクションとReusable Workflowsの利用を許している必要があり、「選択したものだけ許可」にしている場合は呼び出し先を許可リストに加えます。
Organizationで共有するなら、共通ワークフロー専用のリポジトリを1つ作り、そこだけAccessを開放する構成が管理しやすくなります。Enterprise CloudとGHESでは、特定のReusable Workflowしか実行できないセルフホストランナーのグループを作れるため、本番デプロイ用のランナーを共通ワークフロー経由に限定できます。ランナーの構成はGitHubセルフホストランナーとは?導入手順とhosted版の違いで扱っています。GHESからgithub.com上のReusable Workflowsは直接呼べず、GHES内にコピーを置いてそのパスから呼びます。
共通ワークフローの設計、ブランチ保護、デプロイ承認までまとめて整える場合は、弊社のDevOps・CI/CD導入支援でも対応しています。
共通リポジトリのファイルを使う:job.workflow_repositoryとjob.workflow_sha
Reusable Workflowsで長く不便だったのが、呼び出し先リポジトリに置いたスクリプトや設定ファイルの取得です。github コンテキストは常に呼び出し元のものなので、別リポジトリの呼び出し先で actions/checkout を引数なしで実行すると、チェックアウトされるのは呼び出し元のリポジトリです。呼び出し先の中で ref: main のように取得元を直書きすると、呼び出し側がSHAで固定しても共通スクリプトだけ別の版になります。
2026年9月3日のChangelogで、実行中のジョブを定義しているワークフローファイルの情報を返す job コンテキストのプロパティが4つ追加されました。公式のjobコンテキストの説明は次のとおりです。
| プロパティ | 値 | 例 |
|---|---|---|
job.workflow_ref |
ファイルの完全なref | owner/repo/.github/workflows/x.yml@ref |
job.workflow_sha |
ファイルのコミットSHA | 40桁のSHA |
job.workflow_repository |
owner/repo | octo-org/octo-repo |
job.workflow_file_path |
リポジトリ直下からのパス | .github/workflows/deploy.yml |
ワークフローに直接書いたジョブでは job.workflow_ref は github.workflow_ref と同じ値で、違いが出るのはReusable Workflowの中だけです。前章の deploy-reusable.yml は、repository: ${{ job.workflow_repository }} と ref: ${{ job.workflow_sha }} で共通リポジトリを shared ディレクトリに取得し、呼び出し側が固定したコミットのスクリプトを実行します。4つともGHESでは使えません。
共通リポジトリがprivateかinternalの場合は、チェックアウトに使うトークンに注意します。Accessの許可はワークフローとアクションの利用を許す設定で、公式によると GITHUB_TOKEN の権限は「the repository that contains your workflow」に限られます。共通リポジトリの中身を読むには、読み取り権限を持つGitHub Appのトークンなどを actions/checkout の token に渡します。
上限と制約:10段・50本・env非伝播
| 項目 | github.com | GHES |
|---|---|---|
| ネストの深さ | 10段(呼び出し元+9段) | 4段(呼び出し元+3段) |
| 1ファイルから呼べる一意なワークフロー数 | 50本 | 20本 |
| ワークフロー構造のループ | 不可 | 不可 |
github.comの上限は2025年11月6日のChangelogで4段・20本から10段・50本へ引き上げられました。「ネストは4段まで」という説明は、2025年10月以前のgithub.comか現行のGHESの数字です。50本にはネストした呼び出しも含まれ、呼び出し元→1本目→2本目という連鎖は2本と数えます。入れ子のどれか1本でも最初の呼び出し元からアクセスできなければ、ワークフロー全体が失敗します。
envの非伝播とgithubコンテキスト
呼び出し元のワークフローレベルの env は呼び出し先に伝わりません。逆に呼び出し先の env も呼び出し元からは見えず、GITHUB_ENV で呼び出し元のステップへ値を渡すこともできません。呼び出し先へは with で渡し、複数のワークフローで同じ値を使うならOrganization・リポジトリ・環境の変数に置いて vars コンテキストで読みます。
concurrencyのgroup衝突
呼び出し先の ${{ github.workflow }} には呼び出し元のワークフロー名が入ります。そのため、呼び出し元と呼び出し先の両方で concurrency.group: ${{ github.workflow }} と cancel-in-progress: true を使うと、呼び出し先が起動した時点で実行中の呼び出し元がキャンセルされます。公式リファレンスが注意書きで挙げている落とし穴で、呼び出し先のgroupには -deploy のような固定の接尾辞を付けて値をずらします。
GITHUB_TOKENの権限とランナーの課金先
呼び出しジョブに permissions を書かなければ、呼び出し先は、リポジトリなどの既定設定と呼び出し元のワークフローレベルの permissions を反映した GITHUB_TOKEN 権限で動きます。呼び出し先は受け取った権限を維持するか狭めることはできますが、広げることはできません。A→B→Cの入れ子でAが packages: read なら、BとCは packages: write を持てないため、書き込みが必要な呼び出し先があるなら呼び出し元のジョブで先に付与します。
GitHubホストランナーの割り当ては呼び出し元の文脈だけで評価され、課金も常に呼び出し元に付きます。呼び出し先リポジトリ側のGitHubホストランナーは使えません。セルフホストランナーは、呼び出し先が呼び出し元と同じユーザーまたはOrganizationの所有なら、呼び出し元のリポジトリやOrganizationのランナーを使えます。分単価と無料枠はGitHub Actionsの料金で扱っています。
2026年に追加された構文とactionlintの誤検知
$/による同一リポジトリ参照(2026年7月30日)
2026年7月30日のChangelogで、uses を $/ で始める自己リポジトリ参照が追加されました。実行中のコミット時点の自リポジトリに解決され、チェックアウトは要りません。効果が大きいのはアクションの参照で、これまで ./ はチェックアウトが前提だったため、呼び出し側がフルSHAで固定しても内部のアクションが別の版になることがありました。Reusable Workflowの呼び出しでも $/ は推奨の書き方です。ランナーはv2.336.0以降が必要で、@ref は付けられず、GHESでは使えません。
cache-modeによるキャッシュ権限の上限(2026年9月10日)
2026年9月10日のChangelogで、ワークフローまたはジョブ単位でActionsキャッシュへのアクセスを絞る cache-mode が一般提供になりました。
| 値 | 復元 | 保存 |
|---|---|---|
read |
可 | 不可 |
write |
可 | 可 |
write-only |
不可 | 可 |
none |
不可 | 不可 |
省略時は、push のような信頼できるトリガーで write、pull_request_target・issue_comment・workflow_run のような信頼度の低いトリガーで read になります。呼び出しジョブで明示した値(または呼び出し元ワークフローから継承した値)は呼び出し先へ伝播し、呼び出し先がそれを超える値を宣言すると実行が始まらず検証エラーになります。read は復元だけ、write-only は保存だけなので、write-only の呼び出し元から read を宣言した呼び出し先も呼べません。
注意すべきは、呼び出し元が cache-mode を明示していない場合です。このとき呼び出し先は、呼び出し元のトリガーが read 既定でも、write を明示すれば書き込み権限を得られます。外部の呼び出し先を pull_request_target から呼ぶなら、呼び出しジョブに cache-mode: read を書いて上限を固定し、呼び出し先によるキャッシュへの書き込みを防ぎます。
actionlint v1.7.12がエラーにする3つの構文
actionlintの最新リリースは2026年3月30日公開のv1.7.12で、10月31日時点で新版は出ていません。このため2026年に入った3つの構文は、GitHub上では有効でもactionlintではエラーになります。手元でv1.7.12を実行すると、uses: $/... は形式違反、cache-mode は「unexpected key」、job.workflow_repository と job.workflow_sha は「property is not defined in object type」と報告されました。
前の2つはIssue #760($/対応・10月12日起票)とIssue #762(cache-mode・10月23日起票)が未解決のまま開いています。CIでactionlintを必須チェックにしている場合、対応版が出るまでは確認済みの誤検知だけに一致する正規表現を -ignore に指定します。ただし Issue #760 にあるとおり、$/ の呼び出しはinputs・secretsの照合もされないままになるため、その分はレビューで確かめます。導入と設定はactionlintとは?GitHub Actionsワークフローの静的解析ツールの使い方で解説しています。
呼び出し先の版の固定と利用状況の把握
SHA固定とロックファイルの対象範囲
別リポジトリの呼び出し先を @v1 や @main で参照すると、呼び出し先の更新が即座に全呼び出し元へ反映されます。組織内の共通ワークフローなら利点ですが、第三者のワークフローでは中身が差し替わる経路になるため、公式は第三者のワークフローをコミットSHAで参照するよう勧めています。社内の共通リポジトリでは、互換性を保つ間は v1 のようなメジャータグを動かして配る運用もありますが、タグを動かせる人が全利用者のCIを書き換えられる点を権限管理で手当てします。
2026年10月28日にはワークフローのロックファイルがEnterprise Cloud・Team・Freeで一般提供になりました。ただし公式の説明では「It doesn’t pin calls to reusable workflows」とされ、jobs.<job_id>.uses の呼び出しはロックされません。呼び出し先の中で使うアクションは、呼び出し先を定義したリポジトリのロックファイルの対象で、呼び出し元のロックファイルには入りません。ロックファイルを入れても、Reusable Workflowの参照はフルのコミットSHAで書きます。SHA固定の運用とロックファイルの導入はSHA Pinningとは?GitHub ActionsをコミットSHAで固定する方法で扱っています。
再実行時の参照の解決
SHA以外で参照している場合、再実行の挙動が2通りに分かれます。「すべてのジョブを再実行」は指定したrefの現時点の内容を使い、「失敗したジョブを再実行」と「特定のジョブを再実行」は初回実行と同じコミットSHAの呼び出し先を使います。呼び出し先を修正してから失敗ジョブだけを再実行しても、修正は反映されません。
どこから呼ばれているかを監査ログで確認
Enterprise Cloudでは、ワークフローのジョブが始まるたびに監査ログへ prepared_workflow_job が記録されます。calling_workflow_refs には呼び出し元のファイルが呼び出しの逆順で、job_workflow_ref には実際に使われた呼び出し先が入るため、共通ワークフローを変える前に影響範囲を洗い出せます。このデータはREST APIでのみ参照でき、Web画面やJSON・CSVでエクスポートした監査ログには含まれません。
OIDCのjob_workflow_refによる信頼条件の限定
クラウドへのデプロイを共通ワークフローに集約すると、OIDCの信頼条件を「特定の呼び出し先から来たジョブだけ」に絞れます。呼び出し先のジョブが受け取るOIDCトークンでは、repository や ref などの標準クレームは呼び出し元を指し、job_workflow_ref が呼び出し先のファイルとref、job_workflow_sha がそのコミットSHAを示します。公式によると、カスタムクレームとして job_workflow_ref を条件に使えるのはGoogle CloudとHashiCorp Vaultで、aud と sub しか扱えないクラウドでは、APIで sub クレームに job_workflow_ref を含めるようカスタマイズします。AWSでOIDCを使う構成はTerraformをGitHub Actionsで自動化しAWSへ反映する手順で扱っています。
Composite Action・YAMLアンカーとの使い分け
公式の「ワークフロー構成の再利用」は、Reusable Workflowsのほかに、ステップ単位のComposite Actionと、1ファイル内で記述を繰り返さないYAMLアンカーを並べています。
| 観点 | Reusable Workflows | Composite Action | YAMLアンカー |
|---|---|---|---|
| 再利用の単位 | ジョブ | ステップ | YAMLの記述 |
| 使える範囲 | リポジトリ横断 | リポジトリ横断 | 同じファイル内 |
| ランナーの指定 | 呼び出し先 | 呼び出し元ジョブ | 展開先のジョブ |
| シークレット | secretsコンテキスト | inputで受け取る | 通常のジョブと同じ |
| ネスト | 10段(GHESは4段) | 最大10段 | なし |
| Marketplace公開 | 不可 | 可 | 不可 |
| GHES | 使用可 | 使用可 | 使用不可 |
判断基準は、呼び出し元のジョブの中で前後にステップを足す必要があるかどうかです。セットアップとキャッシュ復元のように、ビルド前後に独自のステップを挟む処理はComposite Actionにします。ステージングと本番のデプロイのように、専用のランナーや環境、承認を伴いジョブ単位で完結する処理はReusable Workflowsにします。Reusable Workflowのステップの中でComposite Actionを使う組み合わせもできます。action.ymlの書き方はComposite Action(GitHub Actions)とは?action.ymlの書き方とpost処理の実装で解説しています。
Reusable Workflowsを採用しないほうがよいのは、1つのワークフローファイルの中で同じジョブ設定を2回書いているだけの場合です。YAMLアンカー(&base_job で定義し *base_job で展開)なら、呼び出しジョブの制約や env の非伝播を気にせずに済みます。ステップ単位の共通化をワークフローに切り出すと、呼び出しジョブの後ろにステップを足せないため、呼び出し元のジョブが分割されてかえって読みにくくなります。
Reusable Workflowsに関するよくある質問
workflow_callとは何ですか?
ワークフローを別のワークフローから呼び出せるようにするトリガーです。on に workflow_call を含めたファイルがReusable Workflowになり、その下にinputs・secrets・outputsを宣言します。
workflow_callのinputsで配列やオブジェクトを渡せますか?
型は boolean、number、string の3種類だけなので、直接は渡せません。JSON文字列にしてstringで渡し、呼び出し先で fromJSON() を使って展開します。
secrets: inheritは別のOrganizationのワークフローにも使えますか?
同じEnterpriseに属するOrganizationのワークフローなら使えます。Enterpriseの外にあるワークフローへは、シークレットを secrets で1つずつ明示して渡します。
Reusable Workflowで環境シークレットが空になるのはなぜですか?
原因の一つは、呼び出し元がそのシークレットを渡していないことです。渡していても空になる場合は、呼び出し先ジョブの環境名と、その環境に登録したシークレット名を確認します。呼び出し先のジョブに environment を書いたうえで、呼び出し元は secrets: inherit か名前指定で渡します。呼び出し先で required: true にしておくと、渡し忘れたときに実行が失敗します。
Reusable Workflowのネストは何段までですか?
github.comでは呼び出し元を含めて10段、1ファイルから呼べる一意なワークフローは50本までです。GHESでは4段・20本で、ワークフロー構造のループはどちらでも作れません。