@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.
Files changed (53) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +108 -108
  3. package/errors/_schema.json +89 -89
  4. package/errors/caching-artifacts/artifact-storage-quota-exceeded.yml +118 -0
  5. package/errors/caching-artifacts/cache-miss.yml +56 -56
  6. package/errors/caching-artifacts/cache-save-cancelled-job.yml +82 -0
  7. package/errors/caching-artifacts/cache-v3-to-v4-breaking-changes.yml +95 -0
  8. package/errors/caching-artifacts/cross-repo-artifacts-not-supported.yml +102 -0
  9. package/errors/caching-artifacts/upload-artifact-no-files-found.yml +92 -0
  10. package/errors/caching-artifacts/upload-artifact-v4-breaking.yml +67 -67
  11. package/errors/concurrency-timing/cancel-in-progress-deploy-drops.yml +97 -0
  12. package/errors/concurrency-timing/jobs-cancelled-unexpectedly.yml +60 -60
  13. package/errors/concurrency-timing/skipped-needs-cascade.yml +103 -0
  14. package/errors/concurrency-timing/workflow-run-conclusion-unchecked.yml +100 -0
  15. package/errors/known-unsolved/composite-input-env-vars-missing.yml +91 -0
  16. package/errors/known-unsolved/composite-nested-outputs-null.yml +101 -0
  17. package/errors/known-unsolved/no-dynamic-secret-access.yml +111 -0
  18. package/errors/known-unsolved/no-step-level-rerun.yml +94 -0
  19. package/errors/known-unsolved/no-step-retry.yml +53 -53
  20. package/errors/permissions-auth/checkout-submodule-private-auth.yml +91 -0
  21. package/errors/permissions-auth/fork-pr-secrets-unavailable.yml +97 -0
  22. package/errors/permissions-auth/github-token-403.yml +64 -64
  23. package/errors/permissions-auth/github-token-protected-branch-push.yml +109 -0
  24. package/errors/permissions-auth/oidc-aws-failure.yml +85 -85
  25. package/errors/permissions-auth/oidc-azure-subject-mismatch.yml +91 -0
  26. package/errors/runner-environment/disk-space.yml +57 -57
  27. package/errors/runner-environment/docker-buildx-not-setup.yml +106 -0
  28. package/errors/runner-environment/macos-homebrew-path.yml +90 -0
  29. package/errors/runner-environment/node-runtime-deprecation.yml +56 -56
  30. package/errors/runner-environment/npm-ci-lockfile-mismatch.yml +112 -0
  31. package/errors/runner-environment/self-hosted-stale-toolcache.yml +73 -0
  32. package/errors/runner-environment/setup-node-version-file-missing.yml +105 -0
  33. package/errors/runner-environment/windows-execution-policy.yml +83 -0
  34. package/errors/silent-failures/add-mask-no-retroactive-masking.yml +75 -0
  35. package/errors/silent-failures/composite-boolean-inputs-as-strings.yml +110 -0
  36. package/errors/silent-failures/conditional-output-null-downstream.yml +82 -0
  37. package/errors/silent-failures/continue-on-error-masks-failure.yml +86 -0
  38. package/errors/silent-failures/github-token-no-trigger.yml +57 -57
  39. package/errors/silent-failures/reusable-workflow-env-secrets-empty.yml +90 -0
  40. package/errors/silent-failures/scheduled-workflow-disabled.yml +59 -59
  41. package/errors/triggers/cron-schedule-late.yml +59 -59
  42. package/errors/triggers/pull-request-target-rce-risk.yml +117 -0
  43. package/errors/triggers/workflow-not-triggering.yml +60 -60
  44. package/errors/triggers/workflow-run-default-branch-requirement.yml +78 -0
  45. package/errors/yaml-syntax/anchors-not-supported.yml +95 -0
  46. package/errors/yaml-syntax/dynamic-matrix-fromjson-failure.yml +99 -0
  47. package/errors/yaml-syntax/if-always-true.yml +52 -52
  48. package/errors/yaml-syntax/missing-expression-wrapper.yml +67 -0
  49. package/errors/yaml-syntax/needs-indirect-outputs.yml +91 -0
  50. package/errors/yaml-syntax/secrets-in-if.yml +55 -55
  51. package/errors/yaml-syntax/unexpected-yaml-key.yml +69 -69
  52. package/errors/yaml-syntax/working-directory-ignored-on-uses.yml +66 -0
  53. 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"