@htekdev/actions-debugger 1.0.1 → 1.0.2
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/known-unsolved/workflow-rerun-limit.yml +101 -0
- package/errors/permissions-auth/gcp-oidc-workload-identity-misconfigured.yml +130 -0
- package/errors/runner-environment/node20-to-node24-migration.yml +118 -0
- package/errors/silent-failures/sparse-checkout-sticky-cone-mode.yml +120 -0
- package/errors/yaml-syntax/reusable-workflow-missing-output-declaration.yml +140 -0
- package/package.json +1 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
id: known-unsolved-007
|
|
2
|
+
title: "Workflow Rerun Limit: 50 Reruns Maximum Per Workflow Run"
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- rerun
|
|
7
|
+
- retry
|
|
8
|
+
- automation
|
|
9
|
+
- limits
|
|
10
|
+
- check-suite
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "exceeded.*maximum.*rerun.*limit"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "rerun limit.*50"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "This workflow has been rerun too many times"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "maximum rerun limit.*exceeded"
|
|
19
|
+
flags: "i"
|
|
20
|
+
error_messages:
|
|
21
|
+
- "This workflow has exceeded the maximum rerun limit of 50."
|
|
22
|
+
- "You have exceeded the maximum rerun limit for this workflow run."
|
|
23
|
+
- "Failed check suite: exceeded maximum rerun limit."
|
|
24
|
+
root_cause: |
|
|
25
|
+
In April 2026 GitHub introduced a hard cap of 50 reruns per workflow run (combining
|
|
26
|
+
full re-runs and partial job re-runs). When the limit is hit, any subsequent rerun
|
|
27
|
+
attempt results in a failed check suite with an annotation — no partial or full rerun
|
|
28
|
+
is permitted on that run ever again.
|
|
29
|
+
|
|
30
|
+
The limit was introduced because some automation scripts were issuing hundreds of retry
|
|
31
|
+
attempts on a single workflow run, adding significant load to GitHub's infrastructure.
|
|
32
|
+
|
|
33
|
+
Common triggers of this limit:
|
|
34
|
+
- Automated retry bots (e.g. flaky-test detection scripts) that loop on `gh run rerun`
|
|
35
|
+
- CI platforms that aggressively requeue failed runs
|
|
36
|
+
- Scheduled maintenance pipelines that retry indefinitely on runner-level transient failures
|
|
37
|
+
- GitHub Apps polling and re-triggering stale check suites on long-lived PRs
|
|
38
|
+
fix: |
|
|
39
|
+
There is no way to raise the 50-rerun cap — it is a hard platform limit with no bypass.
|
|
40
|
+
|
|
41
|
+
Recommended approaches:
|
|
42
|
+
1. **Fix the root cause instead of retrying** — identify and address flaky tests, network
|
|
43
|
+
timeouts, or runner instability rather than masking them with reruns.
|
|
44
|
+
2. **Create a new workflow run instead of rerunning** — close and reopen the PR, push a
|
|
45
|
+
no-op commit, or trigger `workflow_dispatch` to start a fresh run with its own 50-run
|
|
46
|
+
budget.
|
|
47
|
+
3. **Implement retry logic inside the step** — use a step-level retry loop (`for i in 1 2 3`)
|
|
48
|
+
rather than whole-workflow reruns so flakiness is contained within a single run.
|
|
49
|
+
4. **Limit automation rerun budgets** — if you operate a retry bot, add a counter check
|
|
50
|
+
(`gh run view --json runAttempt`) and stop retrying once `runAttempt >= 40` to leave
|
|
51
|
+
headroom before the limit hits.
|
|
52
|
+
fix_code:
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: "Step-level retry instead of whole-workflow rerun"
|
|
55
|
+
code: |
|
|
56
|
+
steps:
|
|
57
|
+
- name: Run flaky integration test (with built-in retry)
|
|
58
|
+
shell: bash
|
|
59
|
+
run: |
|
|
60
|
+
for attempt in 1 2 3; do
|
|
61
|
+
echo "Attempt $attempt of 3"
|
|
62
|
+
if npm run test:integration; then
|
|
63
|
+
echo "Tests passed on attempt $attempt"
|
|
64
|
+
exit 0
|
|
65
|
+
fi
|
|
66
|
+
echo "Attempt $attempt failed, retrying..."
|
|
67
|
+
sleep 10
|
|
68
|
+
done
|
|
69
|
+
echo "All 3 attempts failed"
|
|
70
|
+
exit 1
|
|
71
|
+
- language: yaml
|
|
72
|
+
label: "Check rerun count before auto-retrying in a workflow automation"
|
|
73
|
+
code: |
|
|
74
|
+
steps:
|
|
75
|
+
- name: Guard against rerun limit
|
|
76
|
+
shell: bash
|
|
77
|
+
run: |
|
|
78
|
+
RUN_ATTEMPT="${{ github.run_attempt }}"
|
|
79
|
+
if [ "$RUN_ATTEMPT" -ge 40 ]; then
|
|
80
|
+
echo "::error::Run attempt $RUN_ATTEMPT is near the 50-rerun limit. Stopping auto-retry."
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
echo "Run attempt $RUN_ATTEMPT — within safe retry budget"
|
|
84
|
+
- language: yaml
|
|
85
|
+
label: "Trigger a fresh run instead of rerunning (workflow_dispatch)"
|
|
86
|
+
code: |
|
|
87
|
+
# Instead of gh run rerun <run-id>, dispatch a fresh run:
|
|
88
|
+
# gh workflow run my-workflow.yml --ref main
|
|
89
|
+
# This creates a new run ID with its own 50-rerun budget.
|
|
90
|
+
prevention:
|
|
91
|
+
- "Build retry logic inside steps using shell loops rather than whole-workflow reruns."
|
|
92
|
+
- "Monitor `github.run_attempt` context value; alert or stop automation at 40+ attempts."
|
|
93
|
+
- "Fix flaky tests rather than relying on reruns as the primary reliability mechanism."
|
|
94
|
+
- "If a run regularly hits >10 reruns, treat it as a reliability incident, not a normal CI pattern."
|
|
95
|
+
docs:
|
|
96
|
+
- url: "https://github.blog/changelog/2026-04-10-actions-workflows-are-limited-to-50-reruns/"
|
|
97
|
+
label: "GitHub Changelog: Actions workflows are limited to 50 reruns"
|
|
98
|
+
- url: "https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/re-running-workflows-and-jobs"
|
|
99
|
+
label: "Re-running workflows and jobs"
|
|
100
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions"
|
|
101
|
+
label: "Workflow commands for GitHub Actions"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
id: permissions-auth-008
|
|
2
|
+
title: "GCP OIDC Workload Identity: Pool Path vs Provider Path / Project ID vs Number"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- oidc
|
|
7
|
+
- gcp
|
|
8
|
+
- google-cloud
|
|
9
|
+
- workload-identity
|
|
10
|
+
- auth
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "Invalid value for [\"']?audience[\"']?"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "Error code invalid_request.*Invalid value for.*audience"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "workloadIdentityPools.*not.*providers"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "project.*number.*not.*project.*id"
|
|
19
|
+
flags: "i"
|
|
20
|
+
- regex: "OAuthError.*Invalid value for.*audience"
|
|
21
|
+
flags: "i"
|
|
22
|
+
- regex: "Error parsing credentials.*workload_identity_provider"
|
|
23
|
+
flags: "i"
|
|
24
|
+
error_messages:
|
|
25
|
+
- "ERROR: gcloud crashed (OAuthError): Error code invalid_request: Invalid value for \"audience\"."
|
|
26
|
+
- "Error: google-github-actions/auth failed with: Error code invalid_request: Invalid value for \"audience\"."
|
|
27
|
+
- "The workload_identity_provider must be the full provider resource name, not the pool resource name."
|
|
28
|
+
root_cause: |
|
|
29
|
+
`google-github-actions/auth` OIDC authentication fails with "Invalid value for audience"
|
|
30
|
+
when either of two common misconfiguration patterns is present:
|
|
31
|
+
|
|
32
|
+
**Mistake 1 — Pool path instead of Provider path**
|
|
33
|
+
The `workload_identity_provider` input requires the full *Provider* resource name:
|
|
34
|
+
`projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID`
|
|
35
|
+
|
|
36
|
+
Many developers mistakenly supply only the *Pool* path (omitting `/providers/PROVIDER_ID`):
|
|
37
|
+
`projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID`
|
|
38
|
+
|
|
39
|
+
GCP's STS token endpoint rejects the audience because it expects the provider URI, not the pool URI.
|
|
40
|
+
|
|
41
|
+
**Mistake 2 — Project ID (string) instead of Project Number (integer)**
|
|
42
|
+
The path requires the numeric GCP project number (e.g. `123456789012`), NOT the human-readable
|
|
43
|
+
project ID (e.g. `my-gcp-project`). Workload Identity Federation does not accept project IDs.
|
|
44
|
+
Using a project ID causes the STS endpoint to return "Invalid value for audience" because the
|
|
45
|
+
resource path does not resolve to a valid identity provider.
|
|
46
|
+
|
|
47
|
+
**Mistake 3 — Missing `id-token: write` permission**
|
|
48
|
+
If `permissions.id-token` is not explicitly set to `write` at the job or workflow level,
|
|
49
|
+
GitHub will not mint an OIDC token and the auth step fails with a permissions error rather
|
|
50
|
+
than a token exchange error.
|
|
51
|
+
fix: |
|
|
52
|
+
Verify the `workload_identity_provider` value against the exact format required:
|
|
53
|
+
`projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL/providers/PROVIDER`
|
|
54
|
+
|
|
55
|
+
Retrieve the correct value:
|
|
56
|
+
gcloud iam workload-identity-pools providers describe PROVIDER_ID \
|
|
57
|
+
--project=PROJECT_ID \
|
|
58
|
+
--location=global \
|
|
59
|
+
--workload-identity-pool=POOL_ID \
|
|
60
|
+
--format='value(name)'
|
|
61
|
+
|
|
62
|
+
Ensure `id-token: write` is set in the job permissions block.
|
|
63
|
+
|
|
64
|
+
Wait at least 5 minutes after changes to Workload Identity Pool / Provider / IAM bindings
|
|
65
|
+
before testing — these resources are eventually consistent.
|
|
66
|
+
fix_code:
|
|
67
|
+
- language: yaml
|
|
68
|
+
label: "Correct workload_identity_provider format (provider path + project number)"
|
|
69
|
+
code: |
|
|
70
|
+
jobs:
|
|
71
|
+
deploy:
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
permissions:
|
|
74
|
+
id-token: write # REQUIRED — grants GitHub the right to mint an OIDC token
|
|
75
|
+
contents: read
|
|
76
|
+
|
|
77
|
+
steps:
|
|
78
|
+
- uses: actions/checkout@v4
|
|
79
|
+
|
|
80
|
+
- id: auth
|
|
81
|
+
uses: google-github-actions/auth@v2
|
|
82
|
+
with:
|
|
83
|
+
# CORRECT: full provider path with numeric project number
|
|
84
|
+
workload_identity_provider: >-
|
|
85
|
+
projects/123456789012/locations/global/workloadIdentityPools/my-pool/providers/my-provider
|
|
86
|
+
service_account: my-service-account@my-project.iam.gserviceaccount.com
|
|
87
|
+
- language: yaml
|
|
88
|
+
label: "Common mistake — pool path (missing /providers/...) causes audience error"
|
|
89
|
+
code: |
|
|
90
|
+
# WRONG: pool path only — gcloud crashes with "Invalid value for audience"
|
|
91
|
+
# workload_identity_provider: >-
|
|
92
|
+
# projects/123456789012/locations/global/workloadIdentityPools/my-pool
|
|
93
|
+
|
|
94
|
+
# ALSO WRONG: project ID string instead of project number
|
|
95
|
+
# workload_identity_provider: >-
|
|
96
|
+
# projects/my-gcp-project/locations/global/workloadIdentityPools/my-pool/providers/my-provider
|
|
97
|
+
|
|
98
|
+
# CORRECT:
|
|
99
|
+
# workload_identity_provider: >-
|
|
100
|
+
# projects/123456789012/locations/global/workloadIdentityPools/my-pool/providers/my-provider
|
|
101
|
+
- language: yaml
|
|
102
|
+
label: "Store provider path in a repository variable to avoid typos"
|
|
103
|
+
code: |
|
|
104
|
+
jobs:
|
|
105
|
+
deploy:
|
|
106
|
+
runs-on: ubuntu-latest
|
|
107
|
+
permissions:
|
|
108
|
+
id-token: write
|
|
109
|
+
contents: read
|
|
110
|
+
steps:
|
|
111
|
+
- uses: google-github-actions/auth@v2
|
|
112
|
+
with:
|
|
113
|
+
# Store full provider path as a repository variable to avoid typos
|
|
114
|
+
workload_identity_provider: ${{ vars.GCP_WIF_PROVIDER }}
|
|
115
|
+
service_account: ${{ vars.GCP_SERVICE_ACCOUNT }}
|
|
116
|
+
prevention:
|
|
117
|
+
- "Always use `gcloud iam workload-identity-pools providers describe` to copy the exact provider path."
|
|
118
|
+
- "Store the full provider path in a GitHub Actions variable (`vars.GCP_WIF_PROVIDER`) to prevent manual typos."
|
|
119
|
+
- "Use numeric project number — retrieve it with `gcloud projects describe PROJECT_ID --format='value(projectNumber)'`."
|
|
120
|
+
- "After changing Workload Identity Pool config, wait 5 minutes before testing (eventual consistency)."
|
|
121
|
+
- "Confirm `id-token: write` permission is present at job (not just workflow) level."
|
|
122
|
+
docs:
|
|
123
|
+
- url: "https://github.com/google-github-actions/auth#setup"
|
|
124
|
+
label: "google-github-actions/auth: Setup and configuration"
|
|
125
|
+
- url: "https://github.com/google-github-actions/auth/blob/main/docs/TROUBLESHOOTING.md"
|
|
126
|
+
label: "google-github-actions/auth: Troubleshooting guide"
|
|
127
|
+
- url: "https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-google-cloud-platform"
|
|
128
|
+
label: "Configuring OIDC in Google Cloud Platform"
|
|
129
|
+
- url: "https://cloud.google.com/iam/docs/workload-identity-federation-with-deployment-pipelines"
|
|
130
|
+
label: "GCP: Workload Identity Federation with deployment pipelines"
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
id: runner-environment-016
|
|
2
|
+
title: "Node 20 → Node 24 Forced Migration Breaks Actions and macOS 13 Runners"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- node
|
|
7
|
+
- node24
|
|
8
|
+
- deprecation
|
|
9
|
+
- macos
|
|
10
|
+
- arm32
|
|
11
|
+
- runtime-migration
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Node\\.?20 actions are deprecated"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "Please update the following actions to use Node\\.?24"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "node20 is deprecated"
|
|
18
|
+
flags: "i"
|
|
19
|
+
- regex: "macOS 13.*not supported.*Node 24"
|
|
20
|
+
flags: "i"
|
|
21
|
+
- regex: "ARM32.*no longer supported"
|
|
22
|
+
flags: "i"
|
|
23
|
+
error_messages:
|
|
24
|
+
- "Node.js 20 actions are deprecated. Please update the following actions to use Node.js 24."
|
|
25
|
+
- "node20 is deprecated and will be disabled in a future runner release."
|
|
26
|
+
- "Error: This action requires Node.js 24 or higher. Current version: 20."
|
|
27
|
+
root_cause: |
|
|
28
|
+
GitHub announced deprecation of Node 20 on Actions runners on September 19, 2025
|
|
29
|
+
(editor updated May 19, 2026: migration date confirmed June 16, 2026). Starting
|
|
30
|
+
June 16, 2026, all GitHub-hosted runners default to Node 24.
|
|
31
|
+
|
|
32
|
+
Three distinct breakage scenarios exist:
|
|
33
|
+
|
|
34
|
+
1. **Marketplace actions using `runs.using: 'node20'`** — any third-party or custom
|
|
35
|
+
action that declares `runs.using: node20` in its `action.yml` will emit deprecation
|
|
36
|
+
warnings and eventually fail when GitHub removes Node 20 from runners later in 2026.
|
|
37
|
+
|
|
38
|
+
2. **macOS 13 (and older) runners are incompatible with Node 24** — Node 24 dropped
|
|
39
|
+
support for macOS 13.4 and lower. Workflows specifying `runs-on: macos-13` (or older
|
|
40
|
+
images) fail at the runner startup phase or produce unexpected errors from the
|
|
41
|
+
Node-based runner bootstrapper.
|
|
42
|
+
|
|
43
|
+
3. **ARM32 self-hosted runners** — Node 24 has no official ARM32 support. Self-hosted
|
|
44
|
+
runners on ARM32 hardware silently lose the ability to execute Node-based actions
|
|
45
|
+
after the Node 20 removal milestone.
|
|
46
|
+
fix: |
|
|
47
|
+
**For action authors:** Update `action.yml` to declare `runs.using: 'node24'` and
|
|
48
|
+
test locally with Node 24. Publish a new release so downstream consumers pick it up.
|
|
49
|
+
|
|
50
|
+
**For workflow authors:**
|
|
51
|
+
- Upgrade all `uses:` pins to the latest major version that ships with Node 24 support
|
|
52
|
+
(e.g. `actions/checkout@v4 → @v4` already Node-24-ready; `actions/setup-node@v4`
|
|
53
|
+
already Node-24-ready).
|
|
54
|
+
- Replace `runs-on: macos-13` with `runs-on: macos-latest` or `macos-14+`.
|
|
55
|
+
- For ARM32 self-hosted runners: migrate to ARM64 hardware or use container-based
|
|
56
|
+
execution that bundles its own Node runtime.
|
|
57
|
+
|
|
58
|
+
**Temporary escape hatch (not for production long-term):**
|
|
59
|
+
Set `ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true` in the workflow `env` block to
|
|
60
|
+
continue using Node 20 until GitHub removes it from runners later in 2026.
|
|
61
|
+
fix_code:
|
|
62
|
+
- language: yaml
|
|
63
|
+
label: "Upgrade pinned action versions to Node 24-compatible releases"
|
|
64
|
+
code: |
|
|
65
|
+
steps:
|
|
66
|
+
# Pin to latest major — all official actions already ship Node 24 builds
|
|
67
|
+
- uses: actions/checkout@v4
|
|
68
|
+
- uses: actions/setup-node@v4
|
|
69
|
+
with:
|
|
70
|
+
node-version: '20'
|
|
71
|
+
- uses: actions/upload-artifact@v4
|
|
72
|
+
with:
|
|
73
|
+
name: dist
|
|
74
|
+
path: dist/
|
|
75
|
+
- language: yaml
|
|
76
|
+
label: "Migrate macOS runner from macos-13 to macos-latest"
|
|
77
|
+
code: |
|
|
78
|
+
jobs:
|
|
79
|
+
build:
|
|
80
|
+
# macos-13 is incompatible with Node 24 — use macos-latest (14+)
|
|
81
|
+
runs-on: macos-latest
|
|
82
|
+
steps:
|
|
83
|
+
- uses: actions/checkout@v4
|
|
84
|
+
- language: yaml
|
|
85
|
+
label: "Temporary escape hatch — keep Node 20 until explicit removal"
|
|
86
|
+
code: |
|
|
87
|
+
jobs:
|
|
88
|
+
build:
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
env:
|
|
91
|
+
# WARNING: temporary only — Node 20 will be fully removed later in 2026
|
|
92
|
+
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: 'true'
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/checkout@v4
|
|
95
|
+
- language: yaml
|
|
96
|
+
label: "Test Node 24 compatibility before the mandatory cutover"
|
|
97
|
+
code: |
|
|
98
|
+
jobs:
|
|
99
|
+
build:
|
|
100
|
+
runs-on: ubuntu-latest
|
|
101
|
+
env:
|
|
102
|
+
# Force Node 24 to test compatibility ahead of the June 16 cutover
|
|
103
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
|
|
104
|
+
steps:
|
|
105
|
+
- uses: actions/checkout@v4
|
|
106
|
+
prevention:
|
|
107
|
+
- "Subscribe to GitHub changelog https://github.blog/changelog/label/actions/ for deprecation notices."
|
|
108
|
+
- "Run `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true` in CI to catch Node 24 incompatibilities early."
|
|
109
|
+
- "Avoid pinning to old major versions of `actions/*` — use floating major tags (e.g. @v4)."
|
|
110
|
+
- "Audit custom/internal actions for `runs.using: node20` declarations before the June 16, 2026 migration date."
|
|
111
|
+
- "Migrate macOS CI to macos-14 or macos-latest to ensure Node 24 compatibility."
|
|
112
|
+
docs:
|
|
113
|
+
- url: "https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/"
|
|
114
|
+
label: "GitHub Changelog: Deprecation of Node 20 on GitHub Actions runners"
|
|
115
|
+
- url: "https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-javascript-actions"
|
|
116
|
+
label: "Metadata syntax: runs.using for JavaScript actions"
|
|
117
|
+
- url: "https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources"
|
|
118
|
+
label: "Supported runners and hardware resources"
|
|
@@ -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,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