The caller uses the reusable workflow in place of runs-on and steps, passing inputs under with and secrets under secrets, and reads outputs through needs:
jobs:
check:
uses: ./.github/workflows/catalog-check.yml
with:
genre: ${{ inputs.genre }}
secrets:
shelf-key: ${{ secrets.SHELF_API_KEY }}
report:
needs: check
runs-on: ubuntu-24.04
steps:
- run: echo "The reusable workflow counted ${{ needs.check.outputs.count }} books"gh workflow run catalog.yml -f genre=Fiction
gh run view 36136368677 --json jobs --jq '.jobs[] | "\(.name): \(.conclusion)"'
gh api repos/{owner}/{repo}/actions/jobs/108075307016/logs --allow-escape-sequences \
| cut -d' ' -f2- | grep -E '^(##\[group\]Run |##\[notice\]|Node v|added )'Output
https://github.com/binarybehemoth/booknest/actions/runs/36136368677 check / count: success report: success ##[group]Run actions/checkout@v7 ##[group]Run ./.github/actions/setup-booknest ... ##[group]Run ./.github/actions/count-books ##[notice]1 Fiction book(s) in the seed data ... Node v24.21.0, key ***, 1 books
The called job appears as check / count, caller job then callee job. The value travelled through four hops: the JavaScript action's output, the job's outputs, the workflow's outputs under workflow_call, and the caller's needs.check.outputs.count. The secret arrived masked. Secrets must be passed explicitly (or all at once with secrets: inherit, simpler but broader), and the caller's workflow-level env does not cross into the called workflow; vars does.