@htekdev/actions-debugger 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/errors/caching-artifacts/artifact-download-no-artifacts-found.yml +118 -0
- package/errors/known-unsolved/workflow-rerun-limit.yml +101 -0
- package/errors/permissions-auth/gcp-oidc-workload-identity-misconfigured.yml +130 -0
- package/errors/runner-environment/macos-14-sonoma-eol.yml +89 -0
- package/errors/runner-environment/macos-latest-to-macos-26.yml +127 -0
- package/errors/runner-environment/node20-to-node24-migration.yml +118 -0
- package/errors/runner-environment/powershell-74-to-76-upgrade.yml +112 -0
- package/errors/runner-environment/service-container-unhealthy.yml +126 -0
- package/errors/runner-environment/windows-latest-vs2026-migration.yml +131 -0
- package/errors/silent-failures/hashfiles-empty-string-cache-collision.yml +96 -0
- package/errors/silent-failures/sparse-checkout-sticky-cone-mode.yml +120 -0
- package/errors/triggers/environment-protection-rules-silent-block.yml +105 -0
- package/errors/yaml-syntax/env-context-unavailable-job-level.yml +109 -0
- package/errors/yaml-syntax/reusable-workflow-missing-output-declaration.yml +140 -0
- package/package.json +1 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
id: silent-failures-008
|
|
2
|
+
title: "Sparse Checkout Persists to Subsequent Checkout Steps (Sticky Cone Mode)"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- checkout
|
|
7
|
+
- sparse-checkout
|
|
8
|
+
- cone-mode
|
|
9
|
+
- composite-actions
|
|
10
|
+
- file-missing
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "sparse.*checkout.*persist"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "core\\.sparseCheckout.*true"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "sparse-checkout.*not disabled"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "Run actions/checkout@v4"
|
|
20
|
+
- "Expected full checkout but only sparse tree present"
|
|
21
|
+
root_cause: |
|
|
22
|
+
When `actions/checkout` is called with `sparse-checkout:` options, it sets the git
|
|
23
|
+
repository's `core.sparseCheckout = true` config. A subsequent call to `actions/checkout`
|
|
24
|
+
in the same job — even WITHOUT specifying sparse-checkout — re-uses the existing git
|
|
25
|
+
repository and inherits the sticky `core.sparseCheckout = true` setting.
|
|
26
|
+
|
|
27
|
+
Result: the second checkout appears to succeed (no error), but the working directory
|
|
28
|
+
still contains only the sparse subset from the first checkout. Files outside the
|
|
29
|
+
original sparse pattern are silently absent.
|
|
30
|
+
|
|
31
|
+
This most commonly occurs in two scenarios:
|
|
32
|
+
1. A workflow calls `actions/checkout` with sparse-checkout for a fast initial clone,
|
|
33
|
+
then calls `actions/checkout` again (different ref, different path spec) expecting
|
|
34
|
+
a full checkout.
|
|
35
|
+
2. A workflow uses sparse-checkout, then calls a composite action that internally runs
|
|
36
|
+
its own `actions/checkout`. The composite action's checkout inherits the sparse
|
|
37
|
+
setting from the parent workflow's checkout.
|
|
38
|
+
|
|
39
|
+
Root cause: a bug in `actions/checkout` — the `disableSparseCheckout()` method does not
|
|
40
|
+
explicitly set `core.sparseCheckout false` when `sparse-checkout` input is absent. A
|
|
41
|
+
fix PR (#2034) exists in the repo but has not been merged as of 2026.
|
|
42
|
+
fix: |
|
|
43
|
+
**Option 1 (recommended): Explicitly reset sparse-checkout between checkouts**
|
|
44
|
+
Add a `git sparse-checkout disable` step between the sparse and full checkouts. This
|
|
45
|
+
clears the sticky `core.sparseCheckout` flag and ensures subsequent checkouts are full.
|
|
46
|
+
|
|
47
|
+
**Option 2: Use separate `path:` directories**
|
|
48
|
+
Checkout into a unique subdirectory with the `path:` input to prevent git config
|
|
49
|
+
sharing. Each `path:` gets its own `.git` config.
|
|
50
|
+
|
|
51
|
+
**Option 3: Use `sparse-checkout-cone-mode: false` carefully**
|
|
52
|
+
When in non-cone mode, review that patterns don't accidentally match more or fewer
|
|
53
|
+
files than expected. Non-cone mode with incorrect patterns is its own source of
|
|
54
|
+
silent failures.
|
|
55
|
+
fix_code:
|
|
56
|
+
- language: yaml
|
|
57
|
+
label: "Reset sparse-checkout before a subsequent full checkout"
|
|
58
|
+
code: |
|
|
59
|
+
steps:
|
|
60
|
+
# First checkout: sparse (fast clone for config files only)
|
|
61
|
+
- uses: actions/checkout@v4
|
|
62
|
+
with:
|
|
63
|
+
sparse-checkout: |
|
|
64
|
+
.github
|
|
65
|
+
config/
|
|
66
|
+
|
|
67
|
+
- name: Reset sparse-checkout so next checkout is full
|
|
68
|
+
shell: bash
|
|
69
|
+
run: git sparse-checkout disable
|
|
70
|
+
|
|
71
|
+
# Second checkout (in composite action or next step): now gets full tree
|
|
72
|
+
- uses: actions/checkout@v4
|
|
73
|
+
with:
|
|
74
|
+
ref: ${{ github.sha }}
|
|
75
|
+
- language: yaml
|
|
76
|
+
label: "Use separate path: directories to avoid shared git config"
|
|
77
|
+
code: |
|
|
78
|
+
steps:
|
|
79
|
+
# Sparse checkout into 'config-only/' subdirectory
|
|
80
|
+
- uses: actions/checkout@v4
|
|
81
|
+
with:
|
|
82
|
+
path: config-only
|
|
83
|
+
sparse-checkout: |
|
|
84
|
+
config/
|
|
85
|
+
|
|
86
|
+
# Full checkout into 'full-repo/' — completely separate git repo config
|
|
87
|
+
- uses: actions/checkout@v4
|
|
88
|
+
with:
|
|
89
|
+
path: full-repo
|
|
90
|
+
- language: yaml
|
|
91
|
+
label: "Composite action defensive reset (add to composite action's beginning)"
|
|
92
|
+
code: |
|
|
93
|
+
# In your composite action's action.yml — reset sparse before own checkout
|
|
94
|
+
runs:
|
|
95
|
+
using: composite
|
|
96
|
+
steps:
|
|
97
|
+
- name: Reset any inherited sparse-checkout
|
|
98
|
+
shell: bash
|
|
99
|
+
run: |
|
|
100
|
+
if git rev-parse --git-dir > /dev/null 2>&1; then
|
|
101
|
+
git sparse-checkout disable 2>/dev/null || true
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
- uses: actions/checkout@v4
|
|
105
|
+
with:
|
|
106
|
+
ref: ${{ inputs.ref }}
|
|
107
|
+
prevention:
|
|
108
|
+
- "Never assume a subsequent `actions/checkout` step gets a full tree if any earlier step used sparse-checkout."
|
|
109
|
+
- "Add `git sparse-checkout disable` as an explicit step between sparse and full checkouts in the same job."
|
|
110
|
+
- "When writing composite actions that call `actions/checkout`, add a defensive `git sparse-checkout disable` before your checkout step."
|
|
111
|
+
- "Use the `path:` input to isolate checkouts that need different content into separate directories."
|
|
112
|
+
docs:
|
|
113
|
+
- url: "https://github.com/actions/checkout/issues/1498"
|
|
114
|
+
label: "actions/checkout#1498: Sparse checkout persists in composite action"
|
|
115
|
+
- url: "https://github.com/actions/checkout/pull/2034"
|
|
116
|
+
label: "actions/checkout#2034: Fix — disable sparse-checkout on subsequent checkout (open PR)"
|
|
117
|
+
- url: "https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsuses"
|
|
118
|
+
label: "Workflow syntax: steps.uses"
|
|
119
|
+
- url: "https://github.com/actions/checkout#usage"
|
|
120
|
+
label: "actions/checkout: Usage and sparse-checkout options"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
id: triggers-006
|
|
2
|
+
title: "Job Blocked Silently by Environment Protection Rules — No Clear Log Message"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: warning
|
|
5
|
+
tags:
|
|
6
|
+
- environment
|
|
7
|
+
- protection-rules
|
|
8
|
+
- deployment
|
|
9
|
+
- approval
|
|
10
|
+
- branch-restriction
|
|
11
|
+
- silent-wait
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Waiting for.*environment.*approval"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "Branch '.*' is not allowed to deploy to .* due to environment protection rules"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "Expected.*Waiting for status to be reported"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Branch 'feature/my-branch' is not allowed to deploy to production due to environment protection rules."
|
|
21
|
+
- "Waiting for approval — This workflow run requires approval from the environment's configured reviewers."
|
|
22
|
+
- "Expected — Waiting for status to be reported"
|
|
23
|
+
root_cause: |
|
|
24
|
+
GitHub Environments can have protection rules that gate job execution:
|
|
25
|
+
- **Required reviewers**: A human must approve the deployment before the job runs.
|
|
26
|
+
- **Branch/tag restrictions**: Only specific branches or tags are allowed to
|
|
27
|
+
deploy to the environment (e.g., only `main` can deploy to `production`).
|
|
28
|
+
- **Required status checks**: External status checks must pass first.
|
|
29
|
+
- **Wait timers**: A mandatory delay before the job can proceed.
|
|
30
|
+
|
|
31
|
+
When a job targets an environment with unsatisfied protection rules, it enters
|
|
32
|
+
a waiting state in the GitHub UI. The **workflow log may not show a clear
|
|
33
|
+
failure message** — the job simply appears as pending or "Expected — Waiting
|
|
34
|
+
for status to be reported." This is a common source of confusion for teams
|
|
35
|
+
that enable environment protection rules for the first time, especially in
|
|
36
|
+
forks or feature-branch workflows where the branch is not in the allowed list.
|
|
37
|
+
|
|
38
|
+
Documented in GitHub Community discussions #39054 and #26698.
|
|
39
|
+
fix: |
|
|
40
|
+
1. **Check environment settings**: Repository Settings → Environments → select
|
|
41
|
+
the environment → review protection rules (required reviewers, deployment
|
|
42
|
+
branches, required checks, wait timers).
|
|
43
|
+
|
|
44
|
+
2. **For branch restriction failures**: Add the branch that the workflow runs
|
|
45
|
+
on to the environment's "Deployment branches and tags" list, or change the
|
|
46
|
+
policy to "No restriction" for non-production environments.
|
|
47
|
+
|
|
48
|
+
3. **For pending approval**: A configured reviewer must approve the deployment
|
|
49
|
+
in the GitHub UI (Actions tab → select the run → click "Review deployments").
|
|
50
|
+
|
|
51
|
+
4. **For automated CI**: Use a separate environment with no protection rules
|
|
52
|
+
for automated test deployments, and reserve protected environments for
|
|
53
|
+
production deployments that require human approval.
|
|
54
|
+
|
|
55
|
+
5. **For fork PRs**: Environments with protection rules can silently block
|
|
56
|
+
fork PRs since the fork branch is not in the allowed branch list — use a
|
|
57
|
+
separate no-protection environment for fork PR validation jobs.
|
|
58
|
+
fix_code:
|
|
59
|
+
- language: yaml
|
|
60
|
+
label: "Use separate environments for CI vs production"
|
|
61
|
+
code: |
|
|
62
|
+
jobs:
|
|
63
|
+
deploy-staging:
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
environment: staging # ✅ no protection rules — runs immediately
|
|
66
|
+
steps:
|
|
67
|
+
- run: ./deploy.sh staging
|
|
68
|
+
|
|
69
|
+
deploy-production:
|
|
70
|
+
needs: deploy-staging
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
environment: production # 🔒 has required reviewers — waits for approval
|
|
73
|
+
if: github.ref == 'refs/heads/main' # ✅ only main is in allowed branch list
|
|
74
|
+
steps:
|
|
75
|
+
- run: ./deploy.sh production
|
|
76
|
+
- language: yaml
|
|
77
|
+
label: "Check branch restriction: ensure workflow branch is in allowed list"
|
|
78
|
+
code: |
|
|
79
|
+
# In GitHub UI: Settings → Environments → production → Deployment branches
|
|
80
|
+
# Add "main" or change policy to match your workflow's ref.
|
|
81
|
+
#
|
|
82
|
+
# In workflow: use github.ref_name to verify before targeting environment
|
|
83
|
+
jobs:
|
|
84
|
+
deploy:
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
# Only run on main — matches production environment's branch restriction
|
|
87
|
+
if: github.ref == 'refs/heads/main'
|
|
88
|
+
environment: production
|
|
89
|
+
steps:
|
|
90
|
+
- run: ./deploy.sh
|
|
91
|
+
prevention:
|
|
92
|
+
- "Document environment protection rules in your repository's CONTRIBUTING.md so all developers know which branches can deploy where."
|
|
93
|
+
- "Test protection rules in a staging environment before applying them to production — the silent-wait behavior is surprising on first encounter."
|
|
94
|
+
- "For automated CI workflows that run on feature branches, use environments without branch restrictions or required reviewers."
|
|
95
|
+
- "After enabling environment protection rules, verify the workflow can actually trigger by running a test deployment from an allowed branch."
|
|
96
|
+
- "Monitor for stuck jobs in the Actions tab — a job sitting in 'waiting' or 'Expected' state usually means an environment protection rule is blocking it."
|
|
97
|
+
docs:
|
|
98
|
+
- url: "https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment"
|
|
99
|
+
label: "Using environments for deployment — protection rules"
|
|
100
|
+
- url: "https://github.com/orgs/community/discussions/39054"
|
|
101
|
+
label: "GitHub Community #39054 — Branch not allowed to deploy due to environment protection rules"
|
|
102
|
+
- url: "https://github.com/orgs/community/discussions/26698"
|
|
103
|
+
label: "GitHub Community #26698 — Job stuck in Expected / Waiting for status to be reported"
|
|
104
|
+
- url: "https://stackoverflow.com/questions/72109150/github-action-avoid-approval-on-same-environment-rule-within-same-workflow"
|
|
105
|
+
label: "Stack Overflow — Avoiding approval in same environment within same workflow"
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
id: yaml-syntax-014
|
|
2
|
+
title: "env Context Not Available at Job-Level if or Reusable Workflow Positions"
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- env
|
|
7
|
+
- context
|
|
8
|
+
- job-if
|
|
9
|
+
- reusable-workflow
|
|
10
|
+
- expression
|
|
11
|
+
- context-availability
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Unrecognized named-value: 'env'"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "The workflow is not valid.*Unrecognized named-value: 'env'"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "Located at position \\d+ within expression.*env\\."
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "The workflow is not valid. .github/workflows/<workflow>.yml (Line: X, Col: Y): Unrecognized named-value: 'env'. Located at position Z within expression: <expression>"
|
|
21
|
+
- "Unrecognized named-value: 'env'. Located at position 1 within expression"
|
|
22
|
+
root_cause: |
|
|
23
|
+
GitHub Actions evaluates job-level expressions (jobs.<job_id>.if, certain
|
|
24
|
+
jobs.<job_id>.with: inputs for reusable workflows) before any steps run and
|
|
25
|
+
before step-level env values are available. Because the `env` context is
|
|
26
|
+
only populated at step execution time, using `env.MY_VAR` inside a job-level
|
|
27
|
+
`if:` condition or inside a reusable workflow's `jobs:` block triggers a
|
|
28
|
+
validation error at parse time rather than a runtime failure.
|
|
29
|
+
|
|
30
|
+
This affects:
|
|
31
|
+
- `jobs.<job_id>.if: ${{ env.MY_VAR == 'foo' }}`
|
|
32
|
+
- `jobs.<job_id>.with:` fields referencing `env.*` in a reusable workflow call
|
|
33
|
+
- Any top-level workflow expression that attempts to read `env` context
|
|
34
|
+
|
|
35
|
+
Documented in actions/runner issues #1189, #1661, and #2372.
|
|
36
|
+
fix: |
|
|
37
|
+
Replace env context references in job-level positions with contexts that are
|
|
38
|
+
available at job evaluation time:
|
|
39
|
+
- Use `vars.*` (repository/environment variables) for static configuration values
|
|
40
|
+
- Use `github.*` context for event-driven values
|
|
41
|
+
- Use `inputs.*` for values passed into reusable workflows
|
|
42
|
+
- Use job outputs (`needs.<job_id>.outputs.<name>`) to pass dynamic values
|
|
43
|
+
computed in earlier steps to downstream job `if:` conditions
|
|
44
|
+
|
|
45
|
+
If the value is truly dynamic and set in a previous step, emit it as a step
|
|
46
|
+
output → job output → needs output chain so downstream jobs can reference it.
|
|
47
|
+
fix_code:
|
|
48
|
+
- language: yaml
|
|
49
|
+
label: "WRONG — env context in job-level if (fails validation)"
|
|
50
|
+
code: |
|
|
51
|
+
jobs:
|
|
52
|
+
deploy:
|
|
53
|
+
if: ${{ env.DEPLOY_ENV == 'production' }} # ❌ env not available here
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- run: echo "Deploying"
|
|
57
|
+
- language: yaml
|
|
58
|
+
label: "RIGHT — use vars context or job outputs instead"
|
|
59
|
+
code: |
|
|
60
|
+
# Option 1: use vars (repository/environment variables) for static config
|
|
61
|
+
jobs:
|
|
62
|
+
deploy:
|
|
63
|
+
if: ${{ vars.DEPLOY_ENV == 'production' }} # ✅ vars available at job level
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
steps:
|
|
66
|
+
- run: echo "Deploying"
|
|
67
|
+
|
|
68
|
+
# Option 2: compute in a preceding job, emit as output
|
|
69
|
+
jobs:
|
|
70
|
+
compute-env:
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
outputs:
|
|
73
|
+
deploy_env: ${{ steps.set-env.outputs.deploy_env }}
|
|
74
|
+
steps:
|
|
75
|
+
- id: set-env
|
|
76
|
+
run: echo "deploy_env=production" >> $GITHUB_OUTPUT
|
|
77
|
+
|
|
78
|
+
deploy:
|
|
79
|
+
needs: compute-env
|
|
80
|
+
if: ${{ needs.compute-env.outputs.deploy_env == 'production' }}
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
steps:
|
|
83
|
+
- run: echo "Deploying"
|
|
84
|
+
- language: yaml
|
|
85
|
+
label: "RIGHT — reusable workflow: pass value via inputs not env"
|
|
86
|
+
code: |
|
|
87
|
+
# Caller workflow
|
|
88
|
+
jobs:
|
|
89
|
+
call-deploy:
|
|
90
|
+
uses: ./.github/workflows/deploy.yml
|
|
91
|
+
with:
|
|
92
|
+
environment: production # ✅ pass via inputs, not env
|
|
93
|
+
secrets: inherit
|
|
94
|
+
prevention:
|
|
95
|
+
- "Consult the GitHub docs context availability table before writing expressions — not all contexts are available at every YAML key position."
|
|
96
|
+
- "For job-level if conditions, use `vars.*`, `github.*`, or `needs.<job>.outputs.*` — never `env.*`."
|
|
97
|
+
- "For reusable workflow inputs, pass values explicitly through `with:` inputs rather than relying on caller env context."
|
|
98
|
+
- "Use `vars` (repository/environment variables) as a replacement for env-based feature flags that need to be available at job evaluation time."
|
|
99
|
+
docs:
|
|
100
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#context-availability"
|
|
101
|
+
label: "Context availability — which contexts are available at each YAML key"
|
|
102
|
+
- url: "https://github.com/actions/runner/issues/1189"
|
|
103
|
+
label: "actions/runner #1189 — Unrecognized named-value: 'env' for job conditional"
|
|
104
|
+
- url: "https://github.com/actions/runner/issues/1661"
|
|
105
|
+
label: "actions/runner #1661 — env unrecognised in job-level if when calling reusable workflow"
|
|
106
|
+
- url: "https://github.com/actions/runner/issues/2372"
|
|
107
|
+
label: "actions/runner #2372 — Unrecognized named-value: 'env' in reusable workflow jobs"
|
|
108
|
+
- url: "https://stackoverflow.com/questions/76471787/why-is-env-context-not-available-in-github-action-job-level-if-statement"
|
|
109
|
+
label: "Stack Overflow — Why is env context not available in job level if statement?"
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
id: yaml-syntax-013
|
|
2
|
+
title: "Reusable Workflow Output Missing on.workflow_call.outputs Declaration"
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- reusable-workflow
|
|
7
|
+
- workflow-call
|
|
8
|
+
- outputs
|
|
9
|
+
- silent-failure
|
|
10
|
+
- job-outputs
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "needs\\.[a-zA-Z0-9_-]+\\.outputs\\.[a-zA-Z0-9_-]+"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "on\\.workflow_call\\.outputs.*not defined"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "output.*reusable.*workflow.*empty"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "The output variable was not found in the called workflow's on.workflow_call.outputs map."
|
|
20
|
+
- "Output 'version' not found in called workflow."
|
|
21
|
+
root_cause: |
|
|
22
|
+
A called (reusable) workflow exposes job-level outputs but forgets to declare them at
|
|
23
|
+
the `workflow_call` trigger level. As a result the caller workflow's
|
|
24
|
+
`needs.<called-job>.outputs.<name>` expression evaluates to an empty string with NO
|
|
25
|
+
error message — a silent failure.
|
|
26
|
+
|
|
27
|
+
GitHub Actions requires a two-layer output declaration for reusable workflows:
|
|
28
|
+
1. The job inside the called workflow declares step outputs via `outputs:` on the job.
|
|
29
|
+
2. The called workflow's `on.workflow_call.outputs:` section explicitly maps workflow-
|
|
30
|
+
level output names to job-level output expressions.
|
|
31
|
+
|
|
32
|
+
If layer 2 is missing, the caller never receives the value even though layer 1 exists.
|
|
33
|
+
Because the expression resolves to an empty string (not an error), downstream steps may
|
|
34
|
+
silently receive wrong values — version tags become empty strings, Docker image names
|
|
35
|
+
become malformed, deploy environment names become blank.
|
|
36
|
+
|
|
37
|
+
A second variant occurs when accessing outputs from a called workflow that uses a matrix:
|
|
38
|
+
matrix job outputs cannot be aggregated automatically at the workflow level, so outputs
|
|
39
|
+
from individual matrix legs are inaccessible to the caller.
|
|
40
|
+
fix: |
|
|
41
|
+
Add the `on.workflow_call.outputs:` section to the called workflow, mapping each
|
|
42
|
+
desired output name to the corresponding job output expression.
|
|
43
|
+
|
|
44
|
+
For matrix jobs: aggregate outputs into a single job (e.g. using `toJSON`) or use a
|
|
45
|
+
final non-matrix aggregator job inside the called workflow that reads matrix outputs
|
|
46
|
+
and re-exposes them as a single value.
|
|
47
|
+
fix_code:
|
|
48
|
+
- language: yaml
|
|
49
|
+
label: "Called workflow — correct two-layer output declaration"
|
|
50
|
+
code: |
|
|
51
|
+
# .github/workflows/build-and-version.yml (CALLED workflow)
|
|
52
|
+
on:
|
|
53
|
+
workflow_call:
|
|
54
|
+
# Layer 2: workflow-level outputs (REQUIRED for caller to receive values)
|
|
55
|
+
outputs:
|
|
56
|
+
version:
|
|
57
|
+
description: "The computed version string"
|
|
58
|
+
value: ${{ jobs.build.outputs.version }}
|
|
59
|
+
artifact-name:
|
|
60
|
+
description: "Name of the uploaded artifact"
|
|
61
|
+
value: ${{ jobs.build.outputs.artifact-name }}
|
|
62
|
+
|
|
63
|
+
jobs:
|
|
64
|
+
build:
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
# Layer 1: job-level outputs
|
|
67
|
+
outputs:
|
|
68
|
+
version: ${{ steps.compute-version.outputs.version }}
|
|
69
|
+
artifact-name: ${{ steps.upload.outputs.artifact-name }}
|
|
70
|
+
steps:
|
|
71
|
+
- id: compute-version
|
|
72
|
+
run: echo "version=1.2.3-${{ github.sha }}" >> $GITHUB_OUTPUT
|
|
73
|
+
|
|
74
|
+
- id: upload
|
|
75
|
+
uses: actions/upload-artifact@v4
|
|
76
|
+
with:
|
|
77
|
+
name: build-output
|
|
78
|
+
path: dist/
|
|
79
|
+
- language: yaml
|
|
80
|
+
label: "Caller workflow — consuming outputs from called workflow"
|
|
81
|
+
code: |
|
|
82
|
+
# .github/workflows/deploy.yml (CALLER workflow)
|
|
83
|
+
jobs:
|
|
84
|
+
build:
|
|
85
|
+
uses: ./.github/workflows/build-and-version.yml
|
|
86
|
+
secrets: inherit
|
|
87
|
+
|
|
88
|
+
deploy:
|
|
89
|
+
needs: build
|
|
90
|
+
runs-on: ubuntu-latest
|
|
91
|
+
steps:
|
|
92
|
+
- name: Deploy version
|
|
93
|
+
# This only works if the called workflow has on.workflow_call.outputs declared
|
|
94
|
+
run: |
|
|
95
|
+
echo "Deploying version: ${{ needs.build.outputs.version }}"
|
|
96
|
+
echo "Using artifact: ${{ needs.build.outputs.artifact-name }}"
|
|
97
|
+
- language: yaml
|
|
98
|
+
label: "Aggregate matrix outputs in a final job for caller consumption"
|
|
99
|
+
code: |
|
|
100
|
+
# Called workflow with matrix — aggregate results before exposing as outputs
|
|
101
|
+
on:
|
|
102
|
+
workflow_call:
|
|
103
|
+
outputs:
|
|
104
|
+
all-results:
|
|
105
|
+
value: ${{ jobs.aggregate.outputs.results }}
|
|
106
|
+
|
|
107
|
+
jobs:
|
|
108
|
+
test:
|
|
109
|
+
runs-on: ubuntu-latest
|
|
110
|
+
strategy:
|
|
111
|
+
matrix:
|
|
112
|
+
suite: [unit, integration, e2e]
|
|
113
|
+
outputs:
|
|
114
|
+
result-${{ matrix.suite }}: ${{ steps.run.outputs.result }}
|
|
115
|
+
steps:
|
|
116
|
+
- id: run
|
|
117
|
+
run: echo "result=passed" >> $GITHUB_OUTPUT
|
|
118
|
+
|
|
119
|
+
# Aggregator job: collects matrix outputs and re-exposes as single value
|
|
120
|
+
aggregate:
|
|
121
|
+
needs: test
|
|
122
|
+
runs-on: ubuntu-latest
|
|
123
|
+
outputs:
|
|
124
|
+
results: ${{ steps.collect.outputs.results }}
|
|
125
|
+
steps:
|
|
126
|
+
- id: collect
|
|
127
|
+
run: |
|
|
128
|
+
echo "results=${{ toJSON(needs.test.outputs) }}" >> $GITHUB_OUTPUT
|
|
129
|
+
prevention:
|
|
130
|
+
- "Always declare `on.workflow_call.outputs:` in a reusable workflow if any caller needs its outputs."
|
|
131
|
+
- "Test output propagation by printing `${{ needs.<job>.outputs.<name> }}` in a debug step on the caller side."
|
|
132
|
+
- "Treat empty string outputs from called workflows as a sign of missing `on.workflow_call.outputs:` mapping."
|
|
133
|
+
- "Matrix jobs inside called workflows require an aggregator job — matrix outputs cannot be directly mapped."
|
|
134
|
+
docs:
|
|
135
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/reusing-workflows#using-outputs-from-a-reusable-workflow"
|
|
136
|
+
label: "Reusable workflows: using outputs"
|
|
137
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onworkflow_calloutputs"
|
|
138
|
+
label: "Workflow syntax: on.workflow_call.outputs"
|
|
139
|
+
- url: "https://stackoverflow.com/questions/73702333/github-actions-reuse-outputs-from-other-reusable-workflows"
|
|
140
|
+
label: "Stack Overflow: Reuse outputs from reusable workflows (82 upvotes)"
|
package/package.json
CHANGED