@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,110 @@
|
|
|
1
|
+
id: silent-failures-004
|
|
2
|
+
title: "Composite Action Boolean Inputs Treated as Strings — Conditions Always True or Always False"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- composite-actions
|
|
7
|
+
- boolean
|
|
8
|
+
- inputs
|
|
9
|
+
- string-coercion
|
|
10
|
+
- if-condition
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "if:\\s*\\$\\{\\{\\s*inputs\\.[a-zA-Z_-]+\\s*\\}\\}"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "inputs\\.[a-zA-Z_-]+\\s*==\\s*true(?!')"
|
|
15
|
+
flags: "i"
|
|
16
|
+
error_messages:
|
|
17
|
+
- "realRun==false"
|
|
18
|
+
- "Step was skipped because it is conditional"
|
|
19
|
+
root_cause: |
|
|
20
|
+
Composite actions store all inputs internally as strings regardless of the `type:` field declared in
|
|
21
|
+
`action.yml`. The `type: boolean` annotation is accepted by the YAML parser but has no runtime effect —
|
|
22
|
+
all values are coerced to strings before expressions are evaluated.
|
|
23
|
+
|
|
24
|
+
This creates two distinct silent failure modes:
|
|
25
|
+
|
|
26
|
+
1. `if: ${{ inputs.enabled }}` — the string "false" is truthy in GitHub Actions expression syntax,
|
|
27
|
+
so the step ALWAYS runs even when the caller explicitly passes `enabled: false`.
|
|
28
|
+
|
|
29
|
+
2. `if: ${{ inputs.enabled == true }}` — the string "true" never equals the boolean `true` in
|
|
30
|
+
expression syntax, so the step NEVER runs regardless of the input value.
|
|
31
|
+
|
|
32
|
+
In both cases no error is thrown, no warning is emitted, and the workflow succeeds. Only the
|
|
33
|
+
observable behavior is wrong (unexpected steps run or expected steps are skipped entirely).
|
|
34
|
+
|
|
35
|
+
This also affects composite action outputs: any output whose `value:` expression produces a boolean
|
|
36
|
+
(e.g., `${{ fromJSON(steps.x.outputs.flag) }}`) is silently coerced back to a string before it
|
|
37
|
+
leaves the composite action.
|
|
38
|
+
fix: |
|
|
39
|
+
Always compare composite action boolean inputs using string equality:
|
|
40
|
+
|
|
41
|
+
- `if: inputs.enabled == 'true'` ✅
|
|
42
|
+
- `if: ${{ inputs.enabled == 'true' }}` ✅
|
|
43
|
+
- `if: ${{ inputs.enabled }}` ❌ (always truthy — string "false" is truthy)
|
|
44
|
+
- `if: ${{ inputs.enabled == true }}` ❌ (never matches — string never equals boolean)
|
|
45
|
+
|
|
46
|
+
When passing a boolean workflow_dispatch input into a composite action, cast it to a string explicitly:
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
with:
|
|
50
|
+
enabled: ${{ inputs.enabled == true }} # evaluates to string 'true' or 'false'
|
|
51
|
+
```
|
|
52
|
+
fix_code:
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: "Use string comparison for boolean inputs inside composite actions"
|
|
55
|
+
code: |
|
|
56
|
+
# action.yml (composite action)
|
|
57
|
+
inputs:
|
|
58
|
+
enabled:
|
|
59
|
+
description: "Enable the feature"
|
|
60
|
+
required: false
|
|
61
|
+
default: "false"
|
|
62
|
+
# Note: type: boolean has no runtime effect in composite actions — default must be a string
|
|
63
|
+
|
|
64
|
+
runs:
|
|
65
|
+
using: composite
|
|
66
|
+
steps:
|
|
67
|
+
# ❌ WRONG — string "false" is truthy, step always runs
|
|
68
|
+
# - name: Feature step
|
|
69
|
+
# if: ${{ inputs.enabled }}
|
|
70
|
+
|
|
71
|
+
# ❌ WRONG — string "true" != boolean true, step never runs
|
|
72
|
+
# - name: Feature step
|
|
73
|
+
# if: ${{ inputs.enabled == true }}
|
|
74
|
+
|
|
75
|
+
# ✅ CORRECT — compare as string
|
|
76
|
+
- name: Feature step
|
|
77
|
+
if: inputs.enabled == 'true'
|
|
78
|
+
shell: bash
|
|
79
|
+
run: echo "Feature is enabled"
|
|
80
|
+
- language: yaml
|
|
81
|
+
label: "Cast boolean workflow_dispatch input before passing to composite action"
|
|
82
|
+
code: |
|
|
83
|
+
on:
|
|
84
|
+
workflow_dispatch:
|
|
85
|
+
inputs:
|
|
86
|
+
run-tests:
|
|
87
|
+
type: boolean
|
|
88
|
+
default: false
|
|
89
|
+
|
|
90
|
+
jobs:
|
|
91
|
+
build:
|
|
92
|
+
runs-on: ubuntu-latest
|
|
93
|
+
steps:
|
|
94
|
+
- uses: ./.github/actions/my-composite
|
|
95
|
+
with:
|
|
96
|
+
# Explicitly cast to string — inputs.run-tests is boolean at this scope
|
|
97
|
+
run-tests: ${{ inputs.run-tests == true }}
|
|
98
|
+
prevention:
|
|
99
|
+
- "Never rely on `type: boolean` in composite action inputs — it is parsed but not enforced at runtime."
|
|
100
|
+
- "Use string `'false'` not boolean `false` as default values in composite action input definitions."
|
|
101
|
+
- "Always compare composite action boolean inputs with `== 'true'` or `== 'false'` string comparison."
|
|
102
|
+
- "When passing boolean workflow inputs into composite actions, cast with `${{ inputs.flag == true }}`."
|
|
103
|
+
- "Document the string-only convention in the composite action README to prevent future contributors from introducing the bug."
|
|
104
|
+
docs:
|
|
105
|
+
- url: "https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs"
|
|
106
|
+
label: "Metadata syntax — inputs for composite actions"
|
|
107
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions"
|
|
108
|
+
label: "Evaluate expressions in workflows and actions — truthiness rules"
|
|
109
|
+
- url: "https://github.com/actions/runner/issues/2238"
|
|
110
|
+
label: "actions/runner#2238 — Boolean inputs not actually booleans in composite actions (112 reactions)"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
id: silent-failures-007
|
|
2
|
+
title: "Step Output Only Written When Condition Passes — Downstream Reads Empty String"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- outputs
|
|
7
|
+
- steps
|
|
8
|
+
- context
|
|
9
|
+
- conditionals
|
|
10
|
+
- GITHUB_OUTPUT
|
|
11
|
+
- empty-string
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "steps\\.[a-z_-]+\\.outputs\\.[a-z_-]+"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "GITHUB_OUTPUT"
|
|
16
|
+
flags: "i"
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Warning: Unexpected input(s) '', valid inputs are ['version']"
|
|
19
|
+
root_cause: |
|
|
20
|
+
A step output written to `$GITHUB_OUTPUT` (or the deprecated `set-output` command)
|
|
21
|
+
only exists if the step that writes it actually executed **and** reached the `echo`
|
|
22
|
+
statement. When a step is conditional (`if: github.event_name == 'push'`) and the
|
|
23
|
+
condition is false, the step is skipped entirely — no output is written.
|
|
24
|
+
|
|
25
|
+
Downstream steps that reference `${{ steps.<id>.outputs.<key> }}` receive an empty
|
|
26
|
+
string rather than an error, making the failure silent. The workflow continues, but
|
|
27
|
+
subsequent steps silently operate on empty values — potentially deploying with no
|
|
28
|
+
version tag, pushing an empty config, or skipping critical logic without any warning.
|
|
29
|
+
fix: |
|
|
30
|
+
Guard against empty outputs in consuming steps, or restructure so outputs are always
|
|
31
|
+
written unconditionally and logic is controlled by the value itself.
|
|
32
|
+
fix_code:
|
|
33
|
+
- language: yaml
|
|
34
|
+
label: "WRONG — output only written on push, silently empty on PR"
|
|
35
|
+
code: |
|
|
36
|
+
steps:
|
|
37
|
+
- name: Compute version
|
|
38
|
+
id: version
|
|
39
|
+
if: github.event_name == 'push'
|
|
40
|
+
run: echo "tag=v$(date +%Y%m%d%H%M%S)" >> $GITHUB_OUTPUT
|
|
41
|
+
|
|
42
|
+
- name: Build Docker image
|
|
43
|
+
run: |
|
|
44
|
+
# On pull_request, steps.version.outputs.tag is "" — image tagged as ""
|
|
45
|
+
docker build -t myapp:${{ steps.version.outputs.tag }} .
|
|
46
|
+
- language: yaml
|
|
47
|
+
label: "CORRECT — always write output, vary value by condition"
|
|
48
|
+
code: |
|
|
49
|
+
steps:
|
|
50
|
+
- name: Compute version
|
|
51
|
+
id: version
|
|
52
|
+
run: |
|
|
53
|
+
if [ "${{ github.event_name }}" = "push" ]; then
|
|
54
|
+
echo "tag=v$(date +%Y%m%d%H%M%S)" >> $GITHUB_OUTPUT
|
|
55
|
+
else
|
|
56
|
+
echo "tag=dev-${{ github.sha }}" >> $GITHUB_OUTPUT
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
- name: Build Docker image
|
|
60
|
+
run: docker build -t myapp:${{ steps.version.outputs.tag }} .
|
|
61
|
+
- language: yaml
|
|
62
|
+
label: "CORRECT — guard against empty value in consuming step"
|
|
63
|
+
code: |
|
|
64
|
+
steps:
|
|
65
|
+
- name: Deploy
|
|
66
|
+
if: steps.version.outputs.tag != ''
|
|
67
|
+
run: ./deploy.sh ${{ steps.version.outputs.tag }}
|
|
68
|
+
|
|
69
|
+
- name: Warn if no version
|
|
70
|
+
if: steps.version.outputs.tag == ''
|
|
71
|
+
run: |
|
|
72
|
+
echo "::warning::No version tag output — skipping deploy"
|
|
73
|
+
exit 1
|
|
74
|
+
prevention:
|
|
75
|
+
- "Write a default/fallback value in the output step rather than skipping the entire step."
|
|
76
|
+
- "Validate critical outputs with `if: steps.<id>.outputs.<key> != ''` before consuming them."
|
|
77
|
+
- "Use `if: always()` or no condition on steps that must always emit outputs."
|
|
78
|
+
docs:
|
|
79
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs"
|
|
80
|
+
label: "Passing information between jobs"
|
|
81
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-an-output-parameter"
|
|
82
|
+
label: "Setting an output parameter"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
id: silent-failures-005
|
|
2
|
+
title: "continue-on-error: true Masks Step Failures — Job Reports Success"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- continue-on-error
|
|
7
|
+
- failure
|
|
8
|
+
- masking
|
|
9
|
+
- job-status
|
|
10
|
+
- outcome
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "continue-on-error"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "steps\\.[a-z_-]+\\.outcome.*failure"
|
|
15
|
+
flags: "i"
|
|
16
|
+
error_messages:
|
|
17
|
+
- "##[error]Process completed with exit code 1."
|
|
18
|
+
- "Error: Process completed with exit code 1."
|
|
19
|
+
root_cause: |
|
|
20
|
+
When `continue-on-error: true` is set on a step, GitHub Actions marks the step's
|
|
21
|
+
`outcome` as `failure` but sets `conclusion` to `success` and allows the job to
|
|
22
|
+
continue. Critically, the **job itself reports success** even if one or more steps
|
|
23
|
+
failed — the failure is effectively swallowed.
|
|
24
|
+
|
|
25
|
+
This is frequently misunderstood: developers use `continue-on-error` to avoid blocking
|
|
26
|
+
a pipeline on non-critical steps, but they don't realise the overall job will show a
|
|
27
|
+
green checkmark even when those steps fail. Branch protection rules, required status
|
|
28
|
+
checks, and downstream `needs` jobs all see a passing job.
|
|
29
|
+
|
|
30
|
+
The `outcome` context (`steps.<id>.outcome`) still reports `failure`, but this is only
|
|
31
|
+
visible if explicitly checked in a later conditional.
|
|
32
|
+
fix: |
|
|
33
|
+
If you need a step to be non-blocking, check its outcome explicitly in a downstream
|
|
34
|
+
step and surface the failure in a way that's visible (job summary, annotation, or
|
|
35
|
+
dedicated reporting step). Do not rely on `continue-on-error` as a silent suppressor.
|
|
36
|
+
fix_code:
|
|
37
|
+
- language: yaml
|
|
38
|
+
label: "WRONG — failure silently swallowed, job shows green"
|
|
39
|
+
code: |
|
|
40
|
+
steps:
|
|
41
|
+
- name: Run flaky integration test
|
|
42
|
+
id: integration
|
|
43
|
+
continue-on-error: true
|
|
44
|
+
run: ./run-integration.sh
|
|
45
|
+
|
|
46
|
+
- name: Deploy
|
|
47
|
+
run: ./deploy.sh # runs even if integration test failed, job still green
|
|
48
|
+
- language: yaml
|
|
49
|
+
label: "CORRECT — check outcome and fail loudly if needed"
|
|
50
|
+
code: |
|
|
51
|
+
steps:
|
|
52
|
+
- name: Run integration test
|
|
53
|
+
id: integration
|
|
54
|
+
continue-on-error: true
|
|
55
|
+
run: ./run-integration.sh
|
|
56
|
+
|
|
57
|
+
- name: Report integration failure
|
|
58
|
+
if: steps.integration.outcome == 'failure'
|
|
59
|
+
run: |
|
|
60
|
+
echo "::error::Integration test failed — review before shipping"
|
|
61
|
+
echo "## ⚠️ Integration Test Failed" >> $GITHUB_STEP_SUMMARY
|
|
62
|
+
exit 1 # fail the job explicitly if the failure matters
|
|
63
|
+
- language: yaml
|
|
64
|
+
label: "CORRECT — use outcome in branch protection-aware way"
|
|
65
|
+
code: |
|
|
66
|
+
steps:
|
|
67
|
+
- name: Lint (non-blocking)
|
|
68
|
+
id: lint
|
|
69
|
+
continue-on-error: true
|
|
70
|
+
run: npm run lint
|
|
71
|
+
|
|
72
|
+
- name: Annotate lint result
|
|
73
|
+
if: always()
|
|
74
|
+
run: |
|
|
75
|
+
if [ "${{ steps.lint.outcome }}" = "failure" ]; then
|
|
76
|
+
echo "::warning::Linting failed but is non-blocking for this branch"
|
|
77
|
+
fi
|
|
78
|
+
prevention:
|
|
79
|
+
- "Treat `continue-on-error: true` as a visibility-reduction tool — always pair it with explicit outcome checking."
|
|
80
|
+
- "Add a final step that fails the job if any non-critical steps failed and the failure actually matters."
|
|
81
|
+
- "Prefer `allowed-failure` patterns at the matrix level for known-unstable configurations."
|
|
82
|
+
docs:
|
|
83
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error"
|
|
84
|
+
label: "continue-on-error"
|
|
85
|
+
- url: "https://docs.github.com/en/actions/learn-github-actions/expressions#steps-context"
|
|
86
|
+
label: "Steps context — outcome vs conclusion"
|
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
id: silent-failures-002
|
|
2
|
-
title: "GITHUB_TOKEN Cannot Trigger Downstream Workflows"
|
|
3
|
-
category: silent-failures
|
|
4
|
-
severity: silent-failure
|
|
5
|
-
tags:
|
|
6
|
-
- github-token
|
|
7
|
-
- triggers
|
|
8
|
-
- downstream
|
|
9
|
-
- workflow-chain
|
|
10
|
-
- authentication
|
|
11
|
-
patterns:
|
|
12
|
-
- regex: "events triggered by the GITHUB_TOKEN.*will not create a new workflow run"
|
|
13
|
-
flags: "i"
|
|
14
|
-
- regex: "github-actions\\[bot\\]"
|
|
15
|
-
flags: "i"
|
|
16
|
-
- regex: "GITHUB_TOKEN"
|
|
17
|
-
flags: "i"
|
|
18
|
-
error_messages:
|
|
19
|
-
- "Events triggered by the GITHUB_TOKEN will not create a new workflow run."
|
|
20
|
-
root_cause: |
|
|
21
|
-
Commits, tags, and releases created with the repository `GITHUB_TOKEN` are intentionally
|
|
22
|
-
prevented from triggering most downstream workflows. This is a platform safety feature to
|
|
23
|
-
stop accidental recursion and workflow loops.
|
|
24
|
-
|
|
25
|
-
The upstream workflow appears to succeed, but the dependent workflow never starts, which
|
|
26
|
-
makes this feel like a silent trigger failure.
|
|
27
|
-
fix: |
|
|
28
|
-
Use a GitHub App installation token or a PAT when you intentionally need one workflow to
|
|
29
|
-
trigger another via `push`, `create`, or `release`. Keep `GITHUB_TOKEN` for normal repo
|
|
30
|
-
automation that should not fan out into new workflow runs.
|
|
31
|
-
fix_code:
|
|
32
|
-
- language: yaml
|
|
33
|
-
label: "Use a non-GITHUB_TOKEN credential for recursive automation"
|
|
34
|
-
code: |
|
|
35
|
-
jobs:
|
|
36
|
-
release:
|
|
37
|
-
runs-on: ubuntu-latest
|
|
38
|
-
steps:
|
|
39
|
-
- uses: actions/checkout@v4
|
|
40
|
-
- name: Create tag with a PAT
|
|
41
|
-
env:
|
|
42
|
-
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
|
|
43
|
-
run: |
|
|
44
|
-
git tag v${{ github.run_number }}
|
|
45
|
-
git push https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git --tags
|
|
46
|
-
prevention:
|
|
47
|
-
- "Assume `GITHUB_TOKEN` will not fan out into downstream workflow runs unless you are using `workflow_dispatch` or `repository_dispatch`."
|
|
48
|
-
- "Document which automations require a GitHub App token or PAT."
|
|
49
|
-
- "Avoid recursive workflow designs that depend on implicit `push` triggers from `github-actions[bot]`."
|
|
50
|
-
docs:
|
|
51
|
-
- url: "https://docs.github.com/en/actions/security-guides/automatic-token-authentication"
|
|
52
|
-
label: "Automatic token authentication"
|
|
53
|
-
- url: "https://docs.github.com/en/actions/reference/events-that-trigger-workflows"
|
|
54
|
-
label: "Events that trigger workflows"
|
|
55
|
-
source:
|
|
56
|
-
article: "https://htek.dev/articles/github-actions-debugging-guide"
|
|
57
|
-
section: "GITHUB_TOKEN downstream trigger limitations"
|
|
1
|
+
id: silent-failures-002
|
|
2
|
+
title: "GITHUB_TOKEN Cannot Trigger Downstream Workflows"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- github-token
|
|
7
|
+
- triggers
|
|
8
|
+
- downstream
|
|
9
|
+
- workflow-chain
|
|
10
|
+
- authentication
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "events triggered by the GITHUB_TOKEN.*will not create a new workflow run"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "github-actions\\[bot\\]"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "GITHUB_TOKEN"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "Events triggered by the GITHUB_TOKEN will not create a new workflow run."
|
|
20
|
+
root_cause: |
|
|
21
|
+
Commits, tags, and releases created with the repository `GITHUB_TOKEN` are intentionally
|
|
22
|
+
prevented from triggering most downstream workflows. This is a platform safety feature to
|
|
23
|
+
stop accidental recursion and workflow loops.
|
|
24
|
+
|
|
25
|
+
The upstream workflow appears to succeed, but the dependent workflow never starts, which
|
|
26
|
+
makes this feel like a silent trigger failure.
|
|
27
|
+
fix: |
|
|
28
|
+
Use a GitHub App installation token or a PAT when you intentionally need one workflow to
|
|
29
|
+
trigger another via `push`, `create`, or `release`. Keep `GITHUB_TOKEN` for normal repo
|
|
30
|
+
automation that should not fan out into new workflow runs.
|
|
31
|
+
fix_code:
|
|
32
|
+
- language: yaml
|
|
33
|
+
label: "Use a non-GITHUB_TOKEN credential for recursive automation"
|
|
34
|
+
code: |
|
|
35
|
+
jobs:
|
|
36
|
+
release:
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v4
|
|
40
|
+
- name: Create tag with a PAT
|
|
41
|
+
env:
|
|
42
|
+
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
|
|
43
|
+
run: |
|
|
44
|
+
git tag v${{ github.run_number }}
|
|
45
|
+
git push https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git --tags
|
|
46
|
+
prevention:
|
|
47
|
+
- "Assume `GITHUB_TOKEN` will not fan out into downstream workflow runs unless you are using `workflow_dispatch` or `repository_dispatch`."
|
|
48
|
+
- "Document which automations require a GitHub App token or PAT."
|
|
49
|
+
- "Avoid recursive workflow designs that depend on implicit `push` triggers from `github-actions[bot]`."
|
|
50
|
+
docs:
|
|
51
|
+
- url: "https://docs.github.com/en/actions/security-guides/automatic-token-authentication"
|
|
52
|
+
label: "Automatic token authentication"
|
|
53
|
+
- url: "https://docs.github.com/en/actions/reference/events-that-trigger-workflows"
|
|
54
|
+
label: "Events that trigger workflows"
|
|
55
|
+
source:
|
|
56
|
+
article: "https://htek.dev/articles/github-actions-debugging-guide"
|
|
57
|
+
section: "GITHUB_TOKEN downstream trigger limitations"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
id: silent-failures-003
|
|
2
|
+
title: "Reusable Workflow Environment-Scoped Secrets Resolve as Empty String"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- reusable-workflows
|
|
7
|
+
- secrets
|
|
8
|
+
- environments
|
|
9
|
+
- workflow-call
|
|
10
|
+
- secrets-inherit
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "MY_SECRET length: 0"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "secrets\\.[A-Z_]+\\s*:\\s*$"
|
|
15
|
+
flags: "m"
|
|
16
|
+
error_messages:
|
|
17
|
+
- "MY_SECRET length: 0"
|
|
18
|
+
- "EMPTY"
|
|
19
|
+
root_cause: |
|
|
20
|
+
When a reusable workflow (called via `workflow_call`) declares `environment: ${{ inputs.environment_name }}`
|
|
21
|
+
on its job, the GitHub Actions documentation states that environment-scoped secrets should resolve from
|
|
22
|
+
that environment. In practice, secrets resolve to empty string unless the caller adds `secrets: inherit`.
|
|
23
|
+
|
|
24
|
+
The `secrets: inherit` flag passes all of the caller's accessible secrets to the reusable workflow,
|
|
25
|
+
including environment-scoped secrets. Without it, the called workflow's secret resolution context is
|
|
26
|
+
narrowly scoped to explicitly mapped secrets — and environment-scoped secrets are not injected even when
|
|
27
|
+
the job correctly declares the environment name.
|
|
28
|
+
|
|
29
|
+
This is especially dangerous in matrix-based CI/CD pipelines where different matrix entries target
|
|
30
|
+
different environments (e.g., per-tenant deployments). The reusable workflow appears to run normally but
|
|
31
|
+
every secret it tries to use is silently empty. The failure can be masked for days if downstream systems
|
|
32
|
+
tolerate empty credentials during a grace period (e.g., pods using in-cluster Kubernetes Secrets).
|
|
33
|
+
fix: |
|
|
34
|
+
Add `secrets: inherit` to the caller's job definition. This enables the reusable workflow to resolve
|
|
35
|
+
environment-scoped secrets from the environment declared on its job:
|
|
36
|
+
|
|
37
|
+
Alternatively, explicitly enumerate each secret you need to pass via the `secrets:` mapping block.
|
|
38
|
+
The explicit form is more secure for cross-repository reusable workflows.
|
|
39
|
+
fix_code:
|
|
40
|
+
- language: yaml
|
|
41
|
+
label: "Add secrets: inherit so environment-scoped secrets resolve in the reusable workflow"
|
|
42
|
+
code: |
|
|
43
|
+
# Caller workflow
|
|
44
|
+
jobs:
|
|
45
|
+
deploy:
|
|
46
|
+
uses: ./.github/workflows/deploy.yml
|
|
47
|
+
with:
|
|
48
|
+
target_environment: production
|
|
49
|
+
secrets: inherit # required — without this, env-scoped secrets are empty
|
|
50
|
+
|
|
51
|
+
# Reusable workflow (.github/workflows/deploy.yml) — no changes needed
|
|
52
|
+
on:
|
|
53
|
+
workflow_call:
|
|
54
|
+
inputs:
|
|
55
|
+
target_environment:
|
|
56
|
+
required: true
|
|
57
|
+
type: string
|
|
58
|
+
jobs:
|
|
59
|
+
worker:
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
environment: ${{ inputs.target_environment }}
|
|
62
|
+
steps:
|
|
63
|
+
- name: Use secret
|
|
64
|
+
env:
|
|
65
|
+
API_KEY: ${{ secrets.API_KEY }}
|
|
66
|
+
run: |
|
|
67
|
+
echo "API_KEY length: ${#API_KEY}"
|
|
68
|
+
- language: yaml
|
|
69
|
+
label: "Explicit secrets mapping (more secure for cross-repo reusable workflows)"
|
|
70
|
+
code: |
|
|
71
|
+
jobs:
|
|
72
|
+
deploy:
|
|
73
|
+
uses: org/infra/.github/workflows/deploy.yml@main
|
|
74
|
+
with:
|
|
75
|
+
target_environment: production
|
|
76
|
+
secrets:
|
|
77
|
+
API_KEY: ${{ secrets.API_KEY }}
|
|
78
|
+
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
|
79
|
+
prevention:
|
|
80
|
+
- "Always add `secrets: inherit` when a reusable workflow needs environment-scoped secrets."
|
|
81
|
+
- "Verify secret resolution by printing the secret length: `echo \"length ${#MY_SECRET}\"` — never print the value."
|
|
82
|
+
- "In matrix deployments targeting different environments, confirm each matrix item resolves its secrets before shipping."
|
|
83
|
+
- "Consider explicit secrets mapping for cross-repository workflows to avoid accidentally leaking unrelated secrets."
|
|
84
|
+
docs:
|
|
85
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/reusing-workflows#passing-secrets-to-called-workflows"
|
|
86
|
+
label: "Passing secrets to called workflows"
|
|
87
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-environments-for-deployment"
|
|
88
|
+
label: "Using environments for deployment"
|
|
89
|
+
- url: "https://github.com/actions/runner/issues/4453"
|
|
90
|
+
label: "actions/runner#4453 — Environment-scoped secrets unreachable without secrets: inherit"
|
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
id: silent-failures-001
|
|
2
|
-
title: "Scheduled Workflows Silently Disabled After 60 Days"
|
|
3
|
-
category: silent-failures
|
|
4
|
-
severity: silent-failure
|
|
5
|
-
tags:
|
|
6
|
-
- schedule
|
|
7
|
-
- cron
|
|
8
|
-
- inactivity
|
|
9
|
-
- disabled
|
|
10
|
-
- automation
|
|
11
|
-
patterns:
|
|
12
|
-
- regex: "This scheduled workflow is disabled because there hasn't been activity in this repository for at least 60 days"
|
|
13
|
-
flags: "i"
|
|
14
|
-
- regex: "schedule.*workflow.*disabled"
|
|
15
|
-
flags: "i"
|
|
16
|
-
error_messages:
|
|
17
|
-
- "This scheduled workflow is disabled because there hasn't been activity in this repository for at least 60 days"
|
|
18
|
-
root_cause: |
|
|
19
|
-
In public repositories, GitHub automatically disables scheduled workflows after about
|
|
20
|
-
60 days with no repository activity. Nothing in the workflow YAML is technically wrong,
|
|
21
|
-
but the cron job stops firing until someone re-enables it or new activity occurs.
|
|
22
|
-
|
|
23
|
-
This feels like a silent failure because the expected schedule disappears rather than
|
|
24
|
-
producing a normal failed run.
|
|
25
|
-
fix: |
|
|
26
|
-
Re-enable the workflow in the Actions UI and keep the repository active. If the schedule
|
|
27
|
-
must stay alive in a low-activity repository, add a keepalive workflow that periodically
|
|
28
|
-
creates harmless activity.
|
|
29
|
-
fix_code:
|
|
30
|
-
- language: yaml
|
|
31
|
-
label: "Keep scheduled workflows active"
|
|
32
|
-
code: |
|
|
33
|
-
name: Keepalive
|
|
34
|
-
|
|
35
|
-
on:
|
|
36
|
-
schedule:
|
|
37
|
-
- cron: '0 6 * * 1'
|
|
38
|
-
workflow_dispatch:
|
|
39
|
-
|
|
40
|
-
jobs:
|
|
41
|
-
keepalive:
|
|
42
|
-
runs-on: ubuntu-latest
|
|
43
|
-
steps:
|
|
44
|
-
- uses: gautamkrishnar/keepalive-workflow@v2
|
|
45
|
-
with:
|
|
46
|
-
committer_username: github-actions[bot]
|
|
47
|
-
committer_email: 41898282+github-actions[bot]@users.noreply.github.com
|
|
48
|
-
prevention:
|
|
49
|
-
- "Check the Actions tab periodically for disabled scheduled workflows in low-activity repositories."
|
|
50
|
-
- "Pair important cron automations with `workflow_dispatch` for manual recovery."
|
|
51
|
-
- "Use a keepalive strategy for public repos that rely on unattended schedules."
|
|
52
|
-
docs:
|
|
53
|
-
- url: "https://docs.github.com/en/actions/reference/events-that-trigger-workflows#schedule"
|
|
54
|
-
label: "schedule event"
|
|
55
|
-
- url: "https://docs.github.com/en/actions/managing-workflow-runs/disabling-and-enabling-a-workflow"
|
|
56
|
-
label: "Disable and enable a workflow"
|
|
57
|
-
source:
|
|
58
|
-
article: "https://htek.dev/articles/github-actions-debugging-guide"
|
|
59
|
-
section: "Scheduled workflows disabled after inactivity"
|
|
1
|
+
id: silent-failures-001
|
|
2
|
+
title: "Scheduled Workflows Silently Disabled After 60 Days"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- schedule
|
|
7
|
+
- cron
|
|
8
|
+
- inactivity
|
|
9
|
+
- disabled
|
|
10
|
+
- automation
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "This scheduled workflow is disabled because there hasn't been activity in this repository for at least 60 days"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "schedule.*workflow.*disabled"
|
|
15
|
+
flags: "i"
|
|
16
|
+
error_messages:
|
|
17
|
+
- "This scheduled workflow is disabled because there hasn't been activity in this repository for at least 60 days"
|
|
18
|
+
root_cause: |
|
|
19
|
+
In public repositories, GitHub automatically disables scheduled workflows after about
|
|
20
|
+
60 days with no repository activity. Nothing in the workflow YAML is technically wrong,
|
|
21
|
+
but the cron job stops firing until someone re-enables it or new activity occurs.
|
|
22
|
+
|
|
23
|
+
This feels like a silent failure because the expected schedule disappears rather than
|
|
24
|
+
producing a normal failed run.
|
|
25
|
+
fix: |
|
|
26
|
+
Re-enable the workflow in the Actions UI and keep the repository active. If the schedule
|
|
27
|
+
must stay alive in a low-activity repository, add a keepalive workflow that periodically
|
|
28
|
+
creates harmless activity.
|
|
29
|
+
fix_code:
|
|
30
|
+
- language: yaml
|
|
31
|
+
label: "Keep scheduled workflows active"
|
|
32
|
+
code: |
|
|
33
|
+
name: Keepalive
|
|
34
|
+
|
|
35
|
+
on:
|
|
36
|
+
schedule:
|
|
37
|
+
- cron: '0 6 * * 1'
|
|
38
|
+
workflow_dispatch:
|
|
39
|
+
|
|
40
|
+
jobs:
|
|
41
|
+
keepalive:
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
steps:
|
|
44
|
+
- uses: gautamkrishnar/keepalive-workflow@v2
|
|
45
|
+
with:
|
|
46
|
+
committer_username: github-actions[bot]
|
|
47
|
+
committer_email: 41898282+github-actions[bot]@users.noreply.github.com
|
|
48
|
+
prevention:
|
|
49
|
+
- "Check the Actions tab periodically for disabled scheduled workflows in low-activity repositories."
|
|
50
|
+
- "Pair important cron automations with `workflow_dispatch` for manual recovery."
|
|
51
|
+
- "Use a keepalive strategy for public repos that rely on unattended schedules."
|
|
52
|
+
docs:
|
|
53
|
+
- url: "https://docs.github.com/en/actions/reference/events-that-trigger-workflows#schedule"
|
|
54
|
+
label: "schedule event"
|
|
55
|
+
- url: "https://docs.github.com/en/actions/managing-workflow-runs/disabling-and-enabling-a-workflow"
|
|
56
|
+
label: "Disable and enable a workflow"
|
|
57
|
+
source:
|
|
58
|
+
article: "https://htek.dev/articles/github-actions-debugging-guide"
|
|
59
|
+
section: "Scheduled workflows disabled after inactivity"
|