@htekdev/actions-debugger 1.0.0 → 1.0.1
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/LICENSE +21 -21
- package/README.md +108 -108
- package/errors/_schema.json +89 -89
- package/errors/caching-artifacts/artifact-storage-quota-exceeded.yml +118 -0
- package/errors/caching-artifacts/cache-miss.yml +56 -56
- package/errors/caching-artifacts/cache-save-cancelled-job.yml +82 -0
- package/errors/caching-artifacts/cache-v3-to-v4-breaking-changes.yml +95 -0
- package/errors/caching-artifacts/cross-repo-artifacts-not-supported.yml +102 -0
- package/errors/caching-artifacts/upload-artifact-no-files-found.yml +92 -0
- package/errors/caching-artifacts/upload-artifact-v4-breaking.yml +67 -67
- package/errors/concurrency-timing/cancel-in-progress-deploy-drops.yml +97 -0
- package/errors/concurrency-timing/jobs-cancelled-unexpectedly.yml +60 -60
- package/errors/concurrency-timing/skipped-needs-cascade.yml +103 -0
- package/errors/concurrency-timing/workflow-run-conclusion-unchecked.yml +100 -0
- package/errors/known-unsolved/composite-input-env-vars-missing.yml +91 -0
- package/errors/known-unsolved/composite-nested-outputs-null.yml +101 -0
- package/errors/known-unsolved/no-dynamic-secret-access.yml +111 -0
- package/errors/known-unsolved/no-step-level-rerun.yml +94 -0
- package/errors/known-unsolved/no-step-retry.yml +53 -53
- package/errors/permissions-auth/checkout-submodule-private-auth.yml +91 -0
- package/errors/permissions-auth/fork-pr-secrets-unavailable.yml +97 -0
- package/errors/permissions-auth/github-token-403.yml +64 -64
- package/errors/permissions-auth/github-token-protected-branch-push.yml +109 -0
- package/errors/permissions-auth/oidc-aws-failure.yml +85 -85
- package/errors/permissions-auth/oidc-azure-subject-mismatch.yml +91 -0
- package/errors/runner-environment/disk-space.yml +57 -57
- package/errors/runner-environment/docker-buildx-not-setup.yml +106 -0
- package/errors/runner-environment/macos-homebrew-path.yml +90 -0
- package/errors/runner-environment/node-runtime-deprecation.yml +56 -56
- package/errors/runner-environment/npm-ci-lockfile-mismatch.yml +112 -0
- package/errors/runner-environment/self-hosted-stale-toolcache.yml +73 -0
- package/errors/runner-environment/setup-node-version-file-missing.yml +105 -0
- package/errors/runner-environment/windows-execution-policy.yml +83 -0
- package/errors/silent-failures/add-mask-no-retroactive-masking.yml +75 -0
- package/errors/silent-failures/composite-boolean-inputs-as-strings.yml +110 -0
- package/errors/silent-failures/conditional-output-null-downstream.yml +82 -0
- package/errors/silent-failures/continue-on-error-masks-failure.yml +86 -0
- package/errors/silent-failures/github-token-no-trigger.yml +57 -57
- package/errors/silent-failures/reusable-workflow-env-secrets-empty.yml +90 -0
- package/errors/silent-failures/scheduled-workflow-disabled.yml +59 -59
- package/errors/triggers/cron-schedule-late.yml +59 -59
- package/errors/triggers/pull-request-target-rce-risk.yml +117 -0
- package/errors/triggers/workflow-not-triggering.yml +60 -60
- package/errors/triggers/workflow-run-default-branch-requirement.yml +78 -0
- package/errors/yaml-syntax/anchors-not-supported.yml +95 -0
- package/errors/yaml-syntax/dynamic-matrix-fromjson-failure.yml +99 -0
- package/errors/yaml-syntax/if-always-true.yml +52 -52
- package/errors/yaml-syntax/missing-expression-wrapper.yml +67 -0
- package/errors/yaml-syntax/needs-indirect-outputs.yml +91 -0
- package/errors/yaml-syntax/secrets-in-if.yml +55 -55
- package/errors/yaml-syntax/unexpected-yaml-key.yml +69 -69
- package/errors/yaml-syntax/working-directory-ignored-on-uses.yml +66 -0
- package/package.json +70 -67
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
id: concurrency-timing-002
|
|
2
|
+
title: "cancel-in-progress Silently Drops In-Flight Deploy Runs"
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- concurrency
|
|
7
|
+
- cancel-in-progress
|
|
8
|
+
- deployment
|
|
9
|
+
- silent-failure
|
|
10
|
+
- queue
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "Run was cancelled"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "Canceling since a higher priority waiting run was found"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "This run was cancelled by a more recent run"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "Run was cancelled"
|
|
20
|
+
- "Canceling since a higher priority waiting run was found"
|
|
21
|
+
- "This run was cancelled by a more recent run"
|
|
22
|
+
root_cause: |
|
|
23
|
+
When a workflow sets `concurrency.cancel-in-progress: true`, GitHub Actions cancels
|
|
24
|
+
any currently running workflow in the same concurrency group the moment a new run
|
|
25
|
+
starts. This is intentional for CI (cancel stale PR checks), but is dangerous for
|
|
26
|
+
**deploy** workflows where every commit must be deployed in order.
|
|
27
|
+
|
|
28
|
+
The silent failure happens because:
|
|
29
|
+
- The cancelled run is marked "CANCELLED" in the UI — it does not appear as a
|
|
30
|
+
failure, so PR status checks may still pass.
|
|
31
|
+
- If the cancelled run was mid-deploy (e.g., partway through Terraform apply or a
|
|
32
|
+
Kubernetes rollout), the environment is left in a partially-updated state.
|
|
33
|
+
- Developers see the new run succeed and assume all commits were deployed, when in
|
|
34
|
+
reality one or more intermediate commits were silently skipped.
|
|
35
|
+
|
|
36
|
+
A second failure mode occurs with the concurrency queue: GitHub keeps at most one
|
|
37
|
+
*pending* run per group. If a third run starts while one is running and one is pending,
|
|
38
|
+
the older pending run is cancelled to queue the latest one — again silently.
|
|
39
|
+
|
|
40
|
+
Both behaviors are documented but frequently misunderstood when the same workflow
|
|
41
|
+
serves both CI (fast feedback) and CD (reliable delivery) purposes.
|
|
42
|
+
fix: |
|
|
43
|
+
For deploy workflows, set `cancel-in-progress: false` and use a per-branch concurrency
|
|
44
|
+
group so only one deploy runs per branch at a time but no run is silently dropped.
|
|
45
|
+
|
|
46
|
+
If you need cancel-in-progress for PR CI, split your workflow into separate files:
|
|
47
|
+
one for CI (cancel-in-progress: true) and one for deploys (cancel-in-progress: false).
|
|
48
|
+
fix_code:
|
|
49
|
+
- language: yaml
|
|
50
|
+
label: "Safe deploy workflow — never silently cancel"
|
|
51
|
+
code: |
|
|
52
|
+
name: Deploy
|
|
53
|
+
on:
|
|
54
|
+
push:
|
|
55
|
+
branches: [main]
|
|
56
|
+
|
|
57
|
+
concurrency:
|
|
58
|
+
# One deploy at a time per branch, but queue — never cancel
|
|
59
|
+
group: deploy-${{ github.ref }}
|
|
60
|
+
cancel-in-progress: false
|
|
61
|
+
|
|
62
|
+
jobs:
|
|
63
|
+
deploy:
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
steps:
|
|
66
|
+
- uses: actions/checkout@v4
|
|
67
|
+
- run: ./scripts/deploy.sh
|
|
68
|
+
- language: yaml
|
|
69
|
+
label: "Split CI (cancel ok) vs Deploy (queue, no cancel) in one repo"
|
|
70
|
+
code: |
|
|
71
|
+
# .github/workflows/ci.yml — cancel stale PR checks is fine
|
|
72
|
+
name: CI
|
|
73
|
+
on: pull_request
|
|
74
|
+
concurrency:
|
|
75
|
+
group: ci-${{ github.ref }}
|
|
76
|
+
cancel-in-progress: true # OK: PR checks, not deploys
|
|
77
|
+
|
|
78
|
+
# .github/workflows/deploy.yml — never drop a deploy
|
|
79
|
+
name: Deploy
|
|
80
|
+
on:
|
|
81
|
+
push:
|
|
82
|
+
branches: [main, 'release/**']
|
|
83
|
+
concurrency:
|
|
84
|
+
group: deploy-${{ github.ref }}
|
|
85
|
+
cancel-in-progress: false # Queue; never skip a commit
|
|
86
|
+
prevention:
|
|
87
|
+
- "Never use `cancel-in-progress: true` on workflows that deploy, apply infrastructure changes, or produce side effects that must run for every commit."
|
|
88
|
+
- "Monitor the Actions tab for unexpected CANCELLED runs — a CANCELLED deploy job is NOT the same as a skipped/passing check."
|
|
89
|
+
- "Use separate workflow files for CI and CD to apply different concurrency strategies."
|
|
90
|
+
- "Set the concurrency group key to include `github.ref` so branches don't cancel each other."
|
|
91
|
+
docs:
|
|
92
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/using-concurrency"
|
|
93
|
+
label: "Using concurrency"
|
|
94
|
+
- url: "https://github.com/actions/runner/issues/3722"
|
|
95
|
+
label: "actions/runner#3722 — Concurrency cancellation behaviors"
|
|
96
|
+
- url: "https://github.com/orgs/community/discussions/5435"
|
|
97
|
+
label: "Community discussion: cancel-in-progress semantics"
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
id: concurrency-timing-001
|
|
2
|
-
title: "Jobs Cancelled Unexpectedly"
|
|
3
|
-
category: concurrency-timing
|
|
4
|
-
severity: warning
|
|
5
|
-
tags:
|
|
6
|
-
- concurrency
|
|
7
|
-
- cancellation
|
|
8
|
-
- matrix
|
|
9
|
-
- timing
|
|
10
|
-
- branch-isolation
|
|
11
|
-
patterns:
|
|
12
|
-
- regex: "Canceling since a higher priority waiting request for '.+' exists"
|
|
13
|
-
flags: "i"
|
|
14
|
-
- regex: "The workflow run was canceled because another workflow run with the same concurrency group was queued"
|
|
15
|
-
flags: "i"
|
|
16
|
-
- regex: "Operation was canceled\\."
|
|
17
|
-
flags: "i"
|
|
18
|
-
error_messages:
|
|
19
|
-
- "Canceling since a higher priority waiting request for 'deploy' exists"
|
|
20
|
-
- "Operation was canceled."
|
|
21
|
-
root_cause: |
|
|
22
|
-
Concurrency groups are global to the repository unless you scope them yourself. If every
|
|
23
|
-
branch uses the same group name, a push on one branch can cancel a run on a completely
|
|
24
|
-
different branch. Matrix jobs can make this harder to spot because only some legs appear to
|
|
25
|
-
vanish, even though the concurrency rule is what canceled them.
|
|
26
|
-
|
|
27
|
-
The workflow is behaving exactly as configured, but the group name is too broad.
|
|
28
|
-
fix: |
|
|
29
|
-
Include branch or ref information in the concurrency group so only related runs cancel one
|
|
30
|
-
another. Keep `cancel-in-progress: true` only when you truly want a new run to replace the
|
|
31
|
-
older run for the same ref.
|
|
32
|
-
fix_code:
|
|
33
|
-
- language: yaml
|
|
34
|
-
label: "Scope concurrency groups by workflow and ref"
|
|
35
|
-
code: |
|
|
36
|
-
concurrency:
|
|
37
|
-
group: ${{ github.workflow }}-${{ github.ref }}
|
|
38
|
-
cancel-in-progress: true
|
|
39
|
-
|
|
40
|
-
jobs:
|
|
41
|
-
test:
|
|
42
|
-
runs-on: ubuntu-latest
|
|
43
|
-
strategy:
|
|
44
|
-
matrix:
|
|
45
|
-
node: [18, 20]
|
|
46
|
-
steps:
|
|
47
|
-
- uses: actions/checkout@v4
|
|
48
|
-
- run: npm test
|
|
49
|
-
prevention:
|
|
50
|
-
- "Never use a bare group like `deploy` or `ci` unless cross-branch cancellation is intentional."
|
|
51
|
-
- "Include `${{ github.ref }}` or `${{ github.head_ref || github.ref }}` in concurrency groups."
|
|
52
|
-
- "Review concurrency settings any time runs are mysteriously disappearing."
|
|
53
|
-
docs:
|
|
54
|
-
- url: "https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency"
|
|
55
|
-
label: "Control the concurrency of workflows and jobs"
|
|
56
|
-
- url: "https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions"
|
|
57
|
-
label: "Workflow syntax for GitHub Actions"
|
|
58
|
-
source:
|
|
59
|
-
article: "https://htek.dev/articles/github-actions-debugging-guide"
|
|
60
|
-
section: "Unexpected cancellations from concurrency"
|
|
1
|
+
id: concurrency-timing-001
|
|
2
|
+
title: "Jobs Cancelled Unexpectedly"
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: warning
|
|
5
|
+
tags:
|
|
6
|
+
- concurrency
|
|
7
|
+
- cancellation
|
|
8
|
+
- matrix
|
|
9
|
+
- timing
|
|
10
|
+
- branch-isolation
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "Canceling since a higher priority waiting request for '.+' exists"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "The workflow run was canceled because another workflow run with the same concurrency group was queued"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "Operation was canceled\\."
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "Canceling since a higher priority waiting request for 'deploy' exists"
|
|
20
|
+
- "Operation was canceled."
|
|
21
|
+
root_cause: |
|
|
22
|
+
Concurrency groups are global to the repository unless you scope them yourself. If every
|
|
23
|
+
branch uses the same group name, a push on one branch can cancel a run on a completely
|
|
24
|
+
different branch. Matrix jobs can make this harder to spot because only some legs appear to
|
|
25
|
+
vanish, even though the concurrency rule is what canceled them.
|
|
26
|
+
|
|
27
|
+
The workflow is behaving exactly as configured, but the group name is too broad.
|
|
28
|
+
fix: |
|
|
29
|
+
Include branch or ref information in the concurrency group so only related runs cancel one
|
|
30
|
+
another. Keep `cancel-in-progress: true` only when you truly want a new run to replace the
|
|
31
|
+
older run for the same ref.
|
|
32
|
+
fix_code:
|
|
33
|
+
- language: yaml
|
|
34
|
+
label: "Scope concurrency groups by workflow and ref"
|
|
35
|
+
code: |
|
|
36
|
+
concurrency:
|
|
37
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
38
|
+
cancel-in-progress: true
|
|
39
|
+
|
|
40
|
+
jobs:
|
|
41
|
+
test:
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
strategy:
|
|
44
|
+
matrix:
|
|
45
|
+
node: [18, 20]
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v4
|
|
48
|
+
- run: npm test
|
|
49
|
+
prevention:
|
|
50
|
+
- "Never use a bare group like `deploy` or `ci` unless cross-branch cancellation is intentional."
|
|
51
|
+
- "Include `${{ github.ref }}` or `${{ github.head_ref || github.ref }}` in concurrency groups."
|
|
52
|
+
- "Review concurrency settings any time runs are mysteriously disappearing."
|
|
53
|
+
docs:
|
|
54
|
+
- url: "https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency"
|
|
55
|
+
label: "Control the concurrency of workflows and jobs"
|
|
56
|
+
- url: "https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions"
|
|
57
|
+
label: "Workflow syntax for GitHub Actions"
|
|
58
|
+
source:
|
|
59
|
+
article: "https://htek.dev/articles/github-actions-debugging-guide"
|
|
60
|
+
section: "Unexpected cancellations from concurrency"
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
id: concurrency-timing-004
|
|
2
|
+
title: "Skipped needs Job Cascades Skip to All Dependent Jobs"
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- needs
|
|
7
|
+
- skipped
|
|
8
|
+
- cascade
|
|
9
|
+
- conditionals
|
|
10
|
+
- job-status
|
|
11
|
+
- always
|
|
12
|
+
- dependency
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: "skipped.*needs.*skipped"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "This job was skipped"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "Result: skipped"
|
|
19
|
+
flags: "i"
|
|
20
|
+
error_messages:
|
|
21
|
+
- "This job was skipped."
|
|
22
|
+
- "Skipping this job because a previous job in the chain was skipped."
|
|
23
|
+
root_cause: |
|
|
24
|
+
When a job is **skipped** (because its `if:` condition evaluated to false), GitHub
|
|
25
|
+
Actions propagates the skip status to all jobs that `needs` it. Dependent jobs are
|
|
26
|
+
skipped automatically unless they explicitly handle this with `if: always()` or by
|
|
27
|
+
checking `needs.<id>.result == 'skipped'`.
|
|
28
|
+
|
|
29
|
+
This cascades silently: if `build` is skipped on non-main branches, then `test` (which
|
|
30
|
+
needs `build`) is also skipped, and `deploy` (which needs `test`) is also skipped —
|
|
31
|
+
even if `deploy` has no condition of its own and the developer expected it to run.
|
|
32
|
+
|
|
33
|
+
The cascade happens regardless of whether the dependency was skipped by an `if:` guard
|
|
34
|
+
or by the job being cancelled. The default behavior treats a skipped upstream job the
|
|
35
|
+
same as a failed one from the perspective of downstream jobs.
|
|
36
|
+
fix: |
|
|
37
|
+
Use `if: always()` or explicit result checks (`needs.<id>.result == 'success' || needs.<id>.result == 'skipped'`)
|
|
38
|
+
on downstream jobs that should run regardless of whether an upstream job was skipped.
|
|
39
|
+
fix_code:
|
|
40
|
+
- language: yaml
|
|
41
|
+
label: "WRONG — notification job silently skipped when build is skipped"
|
|
42
|
+
code: |
|
|
43
|
+
jobs:
|
|
44
|
+
build:
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
if: github.ref == 'refs/heads/main' # only runs on main
|
|
47
|
+
steps:
|
|
48
|
+
- run: npm run build
|
|
49
|
+
|
|
50
|
+
notify:
|
|
51
|
+
needs: build # skipped when build is skipped (non-main branches)
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
steps:
|
|
54
|
+
- run: ./notify-slack.sh "Build complete"
|
|
55
|
+
- language: yaml
|
|
56
|
+
label: "CORRECT — notify even when build was skipped"
|
|
57
|
+
code: |
|
|
58
|
+
jobs:
|
|
59
|
+
build:
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
if: github.ref == 'refs/heads/main'
|
|
62
|
+
steps:
|
|
63
|
+
- run: npm run build
|
|
64
|
+
|
|
65
|
+
notify:
|
|
66
|
+
needs: build
|
|
67
|
+
if: always() # runs regardless of build's outcome or skip
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
steps:
|
|
70
|
+
- run: |
|
|
71
|
+
RESULT="${{ needs.build.result }}"
|
|
72
|
+
if [ "$RESULT" = "success" ]; then
|
|
73
|
+
./notify-slack.sh "Build succeeded"
|
|
74
|
+
elif [ "$RESULT" = "skipped" ]; then
|
|
75
|
+
echo "Build was skipped (non-main branch) — no notification needed"
|
|
76
|
+
else
|
|
77
|
+
./notify-slack.sh "Build $RESULT"
|
|
78
|
+
fi
|
|
79
|
+
- language: yaml
|
|
80
|
+
label: "CORRECT — final job that always runs regardless of upstream"
|
|
81
|
+
code: |
|
|
82
|
+
jobs:
|
|
83
|
+
deploy:
|
|
84
|
+
needs: [build, test]
|
|
85
|
+
# Run if upstream succeeded OR if they were skipped (allows partial pipelines)
|
|
86
|
+
if: |
|
|
87
|
+
always() &&
|
|
88
|
+
(needs.build.result == 'success' || needs.build.result == 'skipped') &&
|
|
89
|
+
(needs.test.result == 'success' || needs.test.result == 'skipped')
|
|
90
|
+
runs-on: ubuntu-latest
|
|
91
|
+
steps:
|
|
92
|
+
- run: ./deploy.sh
|
|
93
|
+
prevention:
|
|
94
|
+
- "Use `if: always()` on jobs that must run regardless of upstream outcomes (cleanup, notifications, summaries)."
|
|
95
|
+
- "Check `needs.<id>.result` explicitly when a job should behave differently based on upstream skip vs success vs failure."
|
|
96
|
+
- "Document which jobs in your pipeline are optional (skippable) vs required to avoid surprises."
|
|
97
|
+
docs:
|
|
98
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idif"
|
|
99
|
+
label: "jobs.<job_id>.if"
|
|
100
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds"
|
|
101
|
+
label: "jobs.<job_id>.needs"
|
|
102
|
+
- url: "https://docs.github.com/en/actions/learn-github-actions/expressions#status-check-functions"
|
|
103
|
+
label: "Status check functions (always, success, failure, cancelled)"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
id: concurrency-timing-003
|
|
2
|
+
title: "workflow_run Downstream Job Ignores Source Workflow Conclusion"
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- workflow_run
|
|
7
|
+
- conclusion
|
|
8
|
+
- downstream
|
|
9
|
+
- deploy
|
|
10
|
+
- conditional
|
|
11
|
+
- completed
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "github\\.event\\.workflow_run\\.conclusion"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "workflow_run.*completed.*always"
|
|
16
|
+
flags: "i"
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Warning: workflow_run downstream job ran despite source workflow failing."
|
|
19
|
+
root_cause: |
|
|
20
|
+
The `workflow_run` trigger fires when a specified workflow completes, regardless of
|
|
21
|
+
whether that workflow **succeeded or failed**. The trigger type is `completed` — there
|
|
22
|
+
is no built-in `on_success` equivalent. If you do not check
|
|
23
|
+
`github.event.workflow_run.conclusion`, your downstream workflow runs even when the
|
|
24
|
+
upstream workflow failed, was cancelled, or was skipped.
|
|
25
|
+
|
|
26
|
+
This is a common mistake in CD pipelines where a deploy workflow is triggered by a CI
|
|
27
|
+
workflow run. Without a conclusion check, a failed test run triggers the deploy — which
|
|
28
|
+
may deploy broken code, send a notification for a non-event, or waste runner minutes.
|
|
29
|
+
fix: |
|
|
30
|
+
Always add a job-level `if:` condition checking `github.event.workflow_run.conclusion == 'success'`
|
|
31
|
+
in any `workflow_run`-triggered workflow that should only run on success.
|
|
32
|
+
fix_code:
|
|
33
|
+
- language: yaml
|
|
34
|
+
label: "WRONG — deploy runs regardless of CI conclusion"
|
|
35
|
+
code: |
|
|
36
|
+
on:
|
|
37
|
+
workflow_run:
|
|
38
|
+
workflows: ["CI"]
|
|
39
|
+
types: [completed]
|
|
40
|
+
|
|
41
|
+
jobs:
|
|
42
|
+
deploy:
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
# Missing conclusion check — deploys even when CI fails!
|
|
45
|
+
steps:
|
|
46
|
+
- run: ./deploy.sh
|
|
47
|
+
- language: yaml
|
|
48
|
+
label: "CORRECT — gate on success conclusion"
|
|
49
|
+
code: |
|
|
50
|
+
on:
|
|
51
|
+
workflow_run:
|
|
52
|
+
workflows: ["CI"]
|
|
53
|
+
types: [completed]
|
|
54
|
+
|
|
55
|
+
jobs:
|
|
56
|
+
deploy:
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
|
59
|
+
steps:
|
|
60
|
+
- run: ./deploy.sh
|
|
61
|
+
|
|
62
|
+
notify-failure:
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
|
|
65
|
+
steps:
|
|
66
|
+
- run: ./notify-slack.sh "CI failed — no deploy"
|
|
67
|
+
- language: yaml
|
|
68
|
+
label: "CORRECT — handle all outcomes explicitly"
|
|
69
|
+
code: |
|
|
70
|
+
on:
|
|
71
|
+
workflow_run:
|
|
72
|
+
workflows: ["CI"]
|
|
73
|
+
types: [completed]
|
|
74
|
+
|
|
75
|
+
jobs:
|
|
76
|
+
gate:
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
steps:
|
|
79
|
+
- name: Check conclusion
|
|
80
|
+
run: |
|
|
81
|
+
CONCLUSION="${{ github.event.workflow_run.conclusion }}"
|
|
82
|
+
echo "Source workflow concluded: $CONCLUSION"
|
|
83
|
+
if [ "$CONCLUSION" != "success" ]; then
|
|
84
|
+
echo "::error::Upstream CI did not succeed (conclusion: $CONCLUSION) — aborting deploy"
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
deploy:
|
|
89
|
+
needs: gate
|
|
90
|
+
runs-on: ubuntu-latest
|
|
91
|
+
steps:
|
|
92
|
+
- run: ./deploy.sh
|
|
93
|
+
prevention:
|
|
94
|
+
- "Always add `if: github.event.workflow_run.conclusion == 'success'` to any job triggered by `workflow_run: completed`."
|
|
95
|
+
- "Consider adding a separate job for failure notifications to avoid silently swallowing upstream failures."
|
|
96
|
+
docs:
|
|
97
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run"
|
|
98
|
+
label: "workflow_run event — conclusion values"
|
|
99
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow"
|
|
100
|
+
label: "Triggering a workflow from a workflow"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
id: known-unsolved-003
|
|
2
|
+
title: "Composite Actions Do Not Set INPUT_* Environment Variables"
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- composite-actions
|
|
7
|
+
- INPUT_
|
|
8
|
+
- env-vars
|
|
9
|
+
- shell-scripts
|
|
10
|
+
- action-migration
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "\\$INPUT_[A-Z_]+"
|
|
13
|
+
flags: ""
|
|
14
|
+
- regex: "INPUT_[A-Z_]+:\\s*(unbound variable|empty|command not found)"
|
|
15
|
+
flags: "i"
|
|
16
|
+
error_messages:
|
|
17
|
+
- "INPUT_MY_PARAM: unbound variable"
|
|
18
|
+
- "$INPUT_MY_PARAM: empty"
|
|
19
|
+
root_cause: |
|
|
20
|
+
JavaScript actions and Docker container actions receive their inputs as `INPUT_*` environment
|
|
21
|
+
variables at runtime (e.g., the input `my-param` becomes `INPUT_MY-PARAM` or `INPUT_MY_PARAM`).
|
|
22
|
+
Composite actions do NOT follow this convention — they do not set any `INPUT_*` environment
|
|
23
|
+
variables on the runner.
|
|
24
|
+
|
|
25
|
+
Composite actions make inputs available ONLY through the `${{ inputs.name }}` expression context,
|
|
26
|
+
which is evaluated by the Actions runner before the shell command is invoked. Shell scripts
|
|
27
|
+
running inside composite `run:` steps cannot access inputs as `$INPUT_*` environment variables.
|
|
28
|
+
|
|
29
|
+
This limitation has been reported and discussed since 2020 (actions/runner#665, 66+ reactions).
|
|
30
|
+
GitHub has not implemented `INPUT_*` for composite actions. The likely reason is that composite
|
|
31
|
+
actions support multiple shells (bash, powershell, python) and the runner processes inputs as
|
|
32
|
+
templated expressions rather than environment variables for composites.
|
|
33
|
+
|
|
34
|
+
Developers who migrate a shell-script-based action from a JavaScript wrapper (where INPUT_*
|
|
35
|
+
was available via `core.getInput()` delegating to `process.env.INPUT_*`) to a composite action,
|
|
36
|
+
or who copy examples from Docker action documentation, will silently get empty values.
|
|
37
|
+
fix: |
|
|
38
|
+
There is no way to make composite actions set INPUT_* env vars automatically. The workarounds are:
|
|
39
|
+
|
|
40
|
+
Option 1 — inline expression (simple, but creates expression injection risk with untrusted input):
|
|
41
|
+
`run: echo "${{ inputs.my-param }}"`
|
|
42
|
+
|
|
43
|
+
Option 2 — explicit env: mapping on the step (recommended — safe for untrusted input):
|
|
44
|
+
```yaml
|
|
45
|
+
- name: Use input
|
|
46
|
+
env:
|
|
47
|
+
MY_PARAM: ${{ inputs.my-param }}
|
|
48
|
+
run: echo "$MY_PARAM"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Option 3 — convert to a JavaScript action if INPUT_* pattern is deeply embedded in scripts.
|
|
52
|
+
fix_code:
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: "Map composite action inputs to env vars explicitly on each step"
|
|
55
|
+
code: |
|
|
56
|
+
# action.yml (composite action)
|
|
57
|
+
inputs:
|
|
58
|
+
deploy-env:
|
|
59
|
+
description: "Target environment name"
|
|
60
|
+
required: true
|
|
61
|
+
dry-run:
|
|
62
|
+
description: "Whether to perform a dry run"
|
|
63
|
+
required: false
|
|
64
|
+
default: "false"
|
|
65
|
+
|
|
66
|
+
runs:
|
|
67
|
+
using: composite
|
|
68
|
+
steps:
|
|
69
|
+
# ❌ WRONG — INPUT_DEPLOY_ENV and INPUT_DRY_RUN are not set in composite actions
|
|
70
|
+
# - run: ./scripts/deploy.sh "$INPUT_DEPLOY_ENV" "$INPUT_DRY_RUN"
|
|
71
|
+
# shell: bash
|
|
72
|
+
|
|
73
|
+
# ✅ CORRECT — map to env vars in the step's env: block
|
|
74
|
+
- name: Deploy
|
|
75
|
+
env:
|
|
76
|
+
DEPLOY_ENV: ${{ inputs.deploy-env }}
|
|
77
|
+
DRY_RUN: ${{ inputs.dry-run }}
|
|
78
|
+
shell: bash
|
|
79
|
+
run: ./scripts/deploy.sh "$DEPLOY_ENV" "$DRY_RUN"
|
|
80
|
+
prevention:
|
|
81
|
+
- "Never reference `INPUT_*` env vars in composite action shell scripts — they are not set."
|
|
82
|
+
- "When converting a JavaScript or Docker action to composite, audit all `INPUT_*` and `process.env.INPUT_*` references."
|
|
83
|
+
- "Use the `env:` block on each `run:` step to explicitly map inputs to environment variables."
|
|
84
|
+
- "For untrusted user inputs, always use the `env:` mapping pattern rather than inline `${{ inputs.name }}` in shell commands to prevent injection."
|
|
85
|
+
docs:
|
|
86
|
+
- url: "https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-composite-actions"
|
|
87
|
+
label: "Metadata syntax — runs for composite actions"
|
|
88
|
+
- url: "https://docs.github.com/en/actions/creating-actions/creating-a-composite-action"
|
|
89
|
+
label: "Creating a composite action"
|
|
90
|
+
- url: "https://github.com/actions/runner/issues/665"
|
|
91
|
+
label: "actions/runner#665 — INPUT_* env vars missing in composite actions (open since 2020, 66+ reactions)"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
id: known-unsolved-004
|
|
2
|
+
title: "Composite Action Nested Step Outputs Evaluate to null in Calling Workflow"
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- composite-actions
|
|
7
|
+
- nested-actions
|
|
8
|
+
- step-outputs
|
|
9
|
+
- actions-runner
|
|
10
|
+
- known-bug
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "##\\[debug\\]Evaluating: steps\\.[^.]+\\.outputs\\.[^\\s]+"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "##\\[debug\\]=> null"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "value: '' is not"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "##[debug]Evaluating: steps.setter.outputs.some-val"
|
|
20
|
+
- "##[debug]=> null"
|
|
21
|
+
- "Error: value: '' is not 'expected-value'"
|
|
22
|
+
root_cause: |
|
|
23
|
+
When a composite action is nested inside another composite action (A calls B, and B
|
|
24
|
+
exposes step outputs), the runner loses the `steps` context for the inner composite.
|
|
25
|
+
References like `steps.<id>.outputs.<name>` inside the nested composite evaluate to
|
|
26
|
+
`null`, and the outer composite (or the calling workflow) receives an empty string
|
|
27
|
+
instead of the intended output value.
|
|
28
|
+
|
|
29
|
+
The root cause is a context-wiring bug in the Actions runner (documented in
|
|
30
|
+
actions/runner#2009): the runner does not correctly register and propagate the
|
|
31
|
+
`steps` context for nested composite actions, particularly in post-steps. Each
|
|
32
|
+
composite "layer" should maintain its own steps context, but in practice the context
|
|
33
|
+
from the outer layer bleeds through or is lost entirely.
|
|
34
|
+
|
|
35
|
+
A related issue (actions/runner#2030) describes nested composite post-steps inheriting
|
|
36
|
+
the wrong context from the parent action.
|
|
37
|
+
|
|
38
|
+
**This is a known runner limitation.** There is no clean fix — only workarounds.
|
|
39
|
+
The runner team is aware; check the issue threads for status on any official fix.
|
|
40
|
+
fix: |
|
|
41
|
+
There is no direct fix. Use one of these workarounds depending on your situation:
|
|
42
|
+
|
|
43
|
+
**Workaround 1 — Flatten nested composites**: Avoid nesting composite actions when
|
|
44
|
+
step outputs must propagate. Move the logic into a single top-level composite that
|
|
45
|
+
exposes outputs directly to the calling workflow.
|
|
46
|
+
|
|
47
|
+
**Workaround 2 — Use a published remote action**: Reference the inner composite as a
|
|
48
|
+
published action (`uses: owner/repo@ref`) rather than a local nested reference. Some
|
|
49
|
+
reporters find the context bug is avoided when the inner action runs in its own
|
|
50
|
+
repository context.
|
|
51
|
+
|
|
52
|
+
**Workaround 3 — Pass values via environment files**: Instead of `steps.*.outputs.*`,
|
|
53
|
+
write values to `$GITHUB_ENV` or `$GITHUB_OUTPUT` at the top-level composite step and
|
|
54
|
+
read them from there. This sidesteps the `steps` context lookup entirely.
|
|
55
|
+
|
|
56
|
+
**Workaround 4 — Use a JavaScript/Docker action**: If output propagation is critical,
|
|
57
|
+
rewrite the inner composite as a JavaScript or container action, which has correct
|
|
58
|
+
lifecycle and output semantics.
|
|
59
|
+
fix_code:
|
|
60
|
+
- language: yaml
|
|
61
|
+
label: "Flatten: expose output directly from top-level composite"
|
|
62
|
+
code: |
|
|
63
|
+
# action.yml (top-level composite — do NOT nest another composite inside)
|
|
64
|
+
name: My Action
|
|
65
|
+
outputs:
|
|
66
|
+
result:
|
|
67
|
+
description: "The computed result"
|
|
68
|
+
value: ${{ steps.compute.outputs.result }}
|
|
69
|
+
runs:
|
|
70
|
+
using: composite
|
|
71
|
+
steps:
|
|
72
|
+
- id: compute
|
|
73
|
+
shell: bash
|
|
74
|
+
run: echo "result=hello-world" >> $GITHUB_OUTPUT
|
|
75
|
+
- language: yaml
|
|
76
|
+
label: "Workaround: write to GITHUB_OUTPUT from inner step directly"
|
|
77
|
+
code: |
|
|
78
|
+
# Instead of relying on steps.*.outputs.* in a nested composite,
|
|
79
|
+
# write directly to the top-level $GITHUB_OUTPUT from within the step:
|
|
80
|
+
runs:
|
|
81
|
+
using: composite
|
|
82
|
+
steps:
|
|
83
|
+
- shell: bash
|
|
84
|
+
run: |
|
|
85
|
+
COMPUTED=$(./scripts/compute.sh)
|
|
86
|
+
# Write directly to top-level output file
|
|
87
|
+
echo "result=${COMPUTED}" >> $GITHUB_OUTPUT
|
|
88
|
+
prevention:
|
|
89
|
+
- "Avoid nesting composite actions more than one level deep when step outputs must propagate."
|
|
90
|
+
- "Test output propagation explicitly (assert the output equals the expected value) before relying on it in production workflows."
|
|
91
|
+
- "Prefer JavaScript actions over composite actions when reliable output propagation is critical."
|
|
92
|
+
- "Track actions/runner#2009 for an official fix — the limitation may be resolved in a future runner version."
|
|
93
|
+
docs:
|
|
94
|
+
- url: "https://github.com/actions/runner/issues/2009"
|
|
95
|
+
label: "actions/runner#2009 — Composite: Step Outputs not available in nested composite actions"
|
|
96
|
+
- url: "https://github.com/actions/runner/issues/2030"
|
|
97
|
+
label: "actions/runner#2030 — Composite: Nested actions post steps have wrong context"
|
|
98
|
+
- url: "https://github.com/actions/runner/issues/646"
|
|
99
|
+
label: "actions/runner#646 — Composite action feature limitations"
|
|
100
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-composite-action"
|
|
101
|
+
label: "Creating a composite action"
|