2.1.3. workflow_call: Inputs and Secrets for Reusable Workflows
💡 First Principle: workflow_call converts a workflow into a function — it declares a typed parameter list (inputs), an explicit secrets contract (secrets), and a return signature (outputs) — and everything a caller wants it to know must pass through that interface, because nothing is inherited implicitly.
This is the mechanism behind organization-wide standard pipelines, and its interface is exam-dense. The called workflow declares what it accepts:
# .github/workflows/reusable-deploy.yml (the callee)
on:
workflow_call:
inputs:
environment:
type: string # string | number | boolean only
required: true
secrets:
deploy_token:
required: true
outputs:
deployed_url:
description: 'URL of the deployment'
value: ${{ jobs.deploy.outputs.url }}
And the caller invokes it at the job level with uses: — not as a step:
jobs:
call-deploy:
uses: my-org/ci-workflows/.github/workflows/reusable-deploy.yml@v2
with:
environment: production
secrets:
deploy_token: ${{ secrets.PROD_DEPLOY_TOKEN }}
# or, to forward everything the caller has:
# secrets: inherit
Three details carry most of the exam weight. First, workflow_call inputs support only string, number, and boolean — there is no choice type here (that's workflow_dispatch only), a classic swapped-answer distractor. Second, secrets are not inherited by default: either map them explicitly by name, or use secrets: inherit — which passes all of the caller's secrets and should be used deliberately, since it widens what the callee can see. Third, nesting is limited: a chain of reusable workflows may go up to ten levels deep, and a caller's tree may reference at most 50 unique reusable workflows in total.
Outputs flow back up through the job: the callee's job sets an output, the callee's workflow_call.outputs maps it to a workflow-level name, and the caller reads it as needs.call-deploy.outputs.deployed_url.
⚠️ Exam Trap: A reusable workflow is called by a job (jobs.<id>.uses:), never by a step, and that job cannot also define steps: — it either calls the reusable workflow or runs its own steps, not both. Any answer showing uses: ./.github/workflows/x.yml inside a steps: list is wrong; that syntax is for actions.
Reflection Question: secrets: inherit is convenient and works instantly. Describe the blast radius you accept by using it with a reusable workflow maintained by another team.