@htekdev/actions-debugger 1.0.13 → 1.0.15
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/dist/db/search.js +3 -1
- package/dist/db/search.js.map +1 -1
- package/dist/tools/suggest-fix.d.ts.map +1 -1
- package/dist/tools/suggest-fix.js +5 -1
- package/dist/tools/suggest-fix.js.map +1 -1
- package/errors/caching-artifacts/cache-key-too-long.yml +93 -0
- package/errors/caching-artifacts/cache-path-not-exist-skipped.yml +152 -0
- package/errors/caching-artifacts/docker-buildx-gha-cache-capacity.yml +107 -0
- package/errors/caching-artifacts/setup-ruby-bundler-ephemeral-workdir-cache-miss.yml +147 -0
- package/errors/caching-artifacts/upload-artifact-v3-retirement-blocked.yml +123 -0
- package/errors/concurrency-timing/always-cleanup-5min-forced-kill.yml +140 -0
- package/errors/concurrency-timing/concurrency-group-env-context-undefined.yml +99 -0
- package/errors/concurrency-timing/required-check-pending-path-filter-skip.yml +160 -0
- package/errors/concurrency-timing/wait-timer-cancel-in-progress-starvation.yml +125 -0
- package/errors/known-unsolved/composite-action-step-timeout-minutes-ignored.yml +146 -0
- package/errors/known-unsolved/reusable-workflow-no-composite-action-call.yml +116 -0
- package/errors/known-unsolved/schedule-trigger-default-branch-only.yml +113 -0
- package/errors/known-unsolved/secrets-not-allowed-in-if-conditions.yml +149 -0
- package/errors/permissions-auth/dependabot-pr-secrets-unavailable.yml +133 -0
- package/errors/permissions-auth/fine-grained-pat-deployment-write-required.yml +146 -0
- package/errors/permissions-auth/github-app-installation-token-new-format.yml +124 -0
- package/errors/permissions-auth/github-packages-read-requires-packages-permission.yml +128 -0
- package/errors/permissions-auth/oidc-id-token-write-permission-missing.yml +169 -0
- package/errors/permissions-auth/permissions-empty-block-removes-contents-read.yml +97 -0
- package/errors/permissions-auth/reusable-workflow-permissions-not-inherited.yml +114 -0
- package/errors/runner-environment/az-powershell-14-to-15-breaking.yml +108 -0
- package/errors/runner-environment/checkout-windows-ebusy-lock.yml +124 -0
- package/errors/runner-environment/deprecated-action-version-auto-rejected.yml +89 -0
- package/errors/runner-environment/github-hosted-runner-disk-space-full.yml +85 -0
- package/errors/runner-environment/github-path-same-step-not-found.yml +114 -0
- package/errors/runner-environment/github-script-v6-octokit-rest-actions-not-function.yml +87 -0
- package/errors/runner-environment/macos-15-mono-nuget-removed.yml +151 -0
- package/errors/runner-environment/macos-15-xcode-simulator-sdk-policy.yml +141 -0
- package/errors/runner-environment/runner-oom-exit-code-137.yml +117 -0
- package/errors/runner-environment/setup-go-go123-telemetry-cache-failure.yml +92 -0
- package/errors/runner-environment/setup-java-distribution-required.yml +108 -0
- package/errors/runner-environment/ubuntu-2204-precached-docker-removed.yml +110 -0
- package/errors/runner-environment/windows-latest-d-drive-removed.yml +104 -0
- package/errors/runner-environment/windows-msvc-ltcg-mixed-image-versions.yml +112 -0
- package/errors/runner-environment/windows-vs2026-cuda-host-compiler-unsupported.yml +145 -0
- package/errors/silent-failures/app-store-ios26-sdk-required.yml +113 -0
- package/errors/silent-failures/event-commits-empty-on-workflow-dispatch.yml +110 -0
- package/errors/silent-failures/fetch-tags-depth-one-silent-no-op.yml +77 -0
- package/errors/silent-failures/github-env-multiline-value-truncated.yml +127 -0
- package/errors/silent-failures/github-sha-pr-merge-commit-not-head.yml +150 -0
- package/errors/silent-failures/job-output-masked-as-secret-empty.yml +147 -0
- package/errors/silent-failures/upload-artifact-permissions-stripped.yml +98 -0
- package/errors/triggers/pull-request-branches-filter-matches-base-not-head.yml +140 -0
- package/errors/triggers/push-event-fires-on-branch-delete.yml +129 -0
- package/errors/triggers/push-first-commit-before-sha-zeros.yml +160 -0
- package/errors/yaml-syntax/fromjson-empty-string-crash.yml +99 -0
- package/errors/yaml-syntax/if-bang-negation-yaml-tag.yml +145 -0
- package/errors/yaml-syntax/local-action-path-always-top-level.yml +142 -0
- package/package.json +1 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
id: known-unsolved-017
|
|
2
|
+
title: "timeout-minutes Is Silently Ignored on Steps Inside Composite Actions"
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- timeout-minutes
|
|
7
|
+
- composite-action
|
|
8
|
+
- limitation
|
|
9
|
+
- step-timeout
|
|
10
|
+
- composite
|
|
11
|
+
- hung-step
|
|
12
|
+
- action-yml
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: "timeout.?minutes.*composite|using.*composite.*timeout"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "Unexpected value 'timeout-minutes'"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "Unexpected value 'timeout-minutes'"
|
|
20
|
+
root_cause: |
|
|
21
|
+
GitHub Actions supports `timeout-minutes` at the job level and at individual step level
|
|
22
|
+
inside regular workflow files. However, `timeout-minutes` is NOT enforced on steps
|
|
23
|
+
inside composite actions (action.yml files using `runs.using: composite`).
|
|
24
|
+
|
|
25
|
+
Adding `timeout-minutes` to a step within a composite action's `steps:` block is
|
|
26
|
+
accepted by the YAML parser without error, but the timeout is silently NOT applied
|
|
27
|
+
at runtime. A hung step inside a composite action will run until the parent job's
|
|
28
|
+
overall `timeout-minutes` is exhausted — up to 6 hours on GitHub-hosted runners by
|
|
29
|
+
default.
|
|
30
|
+
|
|
31
|
+
This creates a dangerous gap:
|
|
32
|
+
- Network-dependent steps (downloads, API calls, package installs) inside a composite
|
|
33
|
+
action can hang indefinitely if the network is slow or unresponsive
|
|
34
|
+
- `actions/cache` intermittently stalls; wrapping it in a composite action with a
|
|
35
|
+
step `timeout-minutes: 5` provides NO protection
|
|
36
|
+
- Composite action authors cannot provide defensive step timeouts — users of the
|
|
37
|
+
composite action must rely entirely on the calling job's `timeout-minutes`
|
|
38
|
+
- Since the step timeout appears to be set (the YAML is valid), developers believe
|
|
39
|
+
they have a guard when they actually have none
|
|
40
|
+
|
|
41
|
+
The limitation has been tracked and discussed in the community since 2021
|
|
42
|
+
(actions/runner#1979) and remains unresolved as of 2026.
|
|
43
|
+
|
|
44
|
+
Source: actions/runner#1979 — "We would like to see timeout-minutes supported on
|
|
45
|
+
steps in composite actions. Occasionally actions like actions/cache bug out and run
|
|
46
|
+
for the default timeout time (6 hours) which causes unnecessary costs."
|
|
47
|
+
fix: |
|
|
48
|
+
Since step-level timeouts cannot be enforced inside composite actions, use these
|
|
49
|
+
workarounds:
|
|
50
|
+
|
|
51
|
+
1. **Shell-level timeout**: Wrap commands in `timeout <seconds>` (bash) or `Start-Sleep`
|
|
52
|
+
+ `Start-Job` with timeout (PowerShell) to kill hung processes from within the script.
|
|
53
|
+
2. **Job-level `timeout-minutes`**: Set a conservative timeout on the calling job as a
|
|
54
|
+
safety net — it limits the composite action's maximum runtime.
|
|
55
|
+
3. **Step-level timeout in the calling workflow**: When calling a composite action as
|
|
56
|
+
a step in a workflow file, `timeout-minutes` IS supported at the step level in the
|
|
57
|
+
workflow (not inside the composite itself). This provides a per-call timeout.
|
|
58
|
+
4. **Split into multiple reusable actions**: Replace a large composite action with
|
|
59
|
+
multiple smaller ones, each invoked as a separate workflow step — where step-level
|
|
60
|
+
`timeout-minutes` IS enforced.
|
|
61
|
+
fix_code:
|
|
62
|
+
- language: yaml
|
|
63
|
+
label: "WRONG — timeout-minutes inside composite action step is silently ignored"
|
|
64
|
+
code: |
|
|
65
|
+
# action.yml — composite action
|
|
66
|
+
name: "Setup with Timeout"
|
|
67
|
+
runs:
|
|
68
|
+
using: composite
|
|
69
|
+
steps:
|
|
70
|
+
- name: Download dependencies
|
|
71
|
+
timeout-minutes: 5 # ← SILENTLY IGNORED — not enforced at runtime
|
|
72
|
+
shell: bash
|
|
73
|
+
run: curl -o deps.tar.gz "${{ inputs.deps-url }}"
|
|
74
|
+
|
|
75
|
+
- language: yaml
|
|
76
|
+
label: "Workaround: shell-level timeout inside composite action"
|
|
77
|
+
code: |
|
|
78
|
+
# action.yml — composite action
|
|
79
|
+
name: "Setup with Shell Timeout"
|
|
80
|
+
runs:
|
|
81
|
+
using: composite
|
|
82
|
+
steps:
|
|
83
|
+
- name: Download dependencies with shell timeout
|
|
84
|
+
shell: bash
|
|
85
|
+
run: |
|
|
86
|
+
# timeout-minutes on this step would be ignored — use shell timeout instead
|
|
87
|
+
timeout 300 curl -o deps.tar.gz "${{ inputs.deps-url }}" || {
|
|
88
|
+
echo "::error::Download timed out after 5 minutes"
|
|
89
|
+
exit 1
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
- name: Cache restore with timeout guard
|
|
93
|
+
shell: bash
|
|
94
|
+
run: |
|
|
95
|
+
timeout 120 ./restore-cache.sh || {
|
|
96
|
+
echo "::warning::Cache restore timed out — continuing without cache"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
- language: yaml
|
|
100
|
+
label: "Workaround: step-level timeout in the calling WORKFLOW (not inside composite)"
|
|
101
|
+
code: |
|
|
102
|
+
# workflow.yml — timeout-minutes at step level in workflow DOES work
|
|
103
|
+
jobs:
|
|
104
|
+
build:
|
|
105
|
+
runs-on: ubuntu-latest
|
|
106
|
+
timeout-minutes: 30 # Job-level safety net
|
|
107
|
+
steps:
|
|
108
|
+
- uses: actions/checkout@v4
|
|
109
|
+
|
|
110
|
+
# timeout-minutes here is at the WORKFLOW step level — this IS enforced
|
|
111
|
+
- name: Run composite action
|
|
112
|
+
uses: ./.github/actions/my-composite-action
|
|
113
|
+
timeout-minutes: 10 # Works: this is a workflow step, not inside the composite
|
|
114
|
+
|
|
115
|
+
- run: npm run build
|
|
116
|
+
|
|
117
|
+
- language: yaml
|
|
118
|
+
label: "Workaround: split composite into multiple actions — each gets timeout"
|
|
119
|
+
code: |
|
|
120
|
+
# workflow.yml — multiple smaller actions instead of one large composite
|
|
121
|
+
jobs:
|
|
122
|
+
build:
|
|
123
|
+
runs-on: ubuntu-latest
|
|
124
|
+
steps:
|
|
125
|
+
# Each step in the workflow gets enforceable timeout-minutes
|
|
126
|
+
- uses: ./.github/actions/setup-node
|
|
127
|
+
timeout-minutes: 5 # Enforced — workflow step level
|
|
128
|
+
|
|
129
|
+
- uses: ./.github/actions/restore-cache
|
|
130
|
+
timeout-minutes: 3 # Enforced
|
|
131
|
+
|
|
132
|
+
- uses: ./.github/actions/install-deps
|
|
133
|
+
timeout-minutes: 10 # Enforced
|
|
134
|
+
prevention:
|
|
135
|
+
- "Never rely on `timeout-minutes` inside composite action `steps:` — the field is accepted but silently ignored."
|
|
136
|
+
- "Use `timeout <seconds>` (bash) or PowerShell job timeouts as shell-level guards for long-running composite steps."
|
|
137
|
+
- "Apply `timeout-minutes` at the step level in the CALLING WORKFLOW (not inside the composite) for per-invocation limits."
|
|
138
|
+
- "Always set a job-level `timeout-minutes` on any job that calls composite actions, as a safety net against runaway steps."
|
|
139
|
+
- "Composite action authors should document the expected maximum runtime so callers can set appropriate job-level timeouts."
|
|
140
|
+
docs:
|
|
141
|
+
- url: "https://github.com/actions/runner/issues/1979"
|
|
142
|
+
label: "actions/runner#1979 — timeout-minutes not enforced in composite action steps (open since 2021)"
|
|
143
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstepstimeout-minutes"
|
|
144
|
+
label: "GitHub Docs: steps[*].timeout-minutes (workflow-level steps — works)"
|
|
145
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#runs-for-composite-actions"
|
|
146
|
+
label: "GitHub Docs: Metadata syntax for composite actions"
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
id: known-unsolved-014
|
|
2
|
+
title: "Reusable Workflows Cannot Be Called from Composite Actions"
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- composite-action
|
|
7
|
+
- reusable-workflow
|
|
8
|
+
- workflow-call
|
|
9
|
+
- limitation
|
|
10
|
+
- uses-key
|
|
11
|
+
- no-fix
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Reusable workflows cannot be referenced from composite actions"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "uses:.*\\.github/workflows/.*\\.yml.*composite"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "A reusable workflow cannot be used as a step in a composite action"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Error: Reusable workflows cannot be referenced from composite actions."
|
|
21
|
+
- "A reusable workflow cannot be used as a step in a composite action."
|
|
22
|
+
root_cause: |
|
|
23
|
+
GitHub Actions enforces a strict separation between two different abstraction levels:
|
|
24
|
+
- **Actions** (composite, JavaScript, Docker) — called from workflow steps via `uses:`
|
|
25
|
+
- **Reusable workflows** (called via `workflow_call` trigger) — called from workflow jobs
|
|
26
|
+
via `uses:` at the JOB level, not the step level
|
|
27
|
+
|
|
28
|
+
A composite action runs within a step inside a job. A reusable workflow, however, IS a
|
|
29
|
+
job (or set of jobs). You cannot embed a job inside a step — this is a fundamental
|
|
30
|
+
architectural constraint, not a configuration error.
|
|
31
|
+
|
|
32
|
+
This fails at validation time with a clear error message. The error is triggered when:
|
|
33
|
+
- A composite action's `runs.steps` contains a `uses:` entry that points to a
|
|
34
|
+
`.github/workflows/*.yml` file (a reusable workflow)
|
|
35
|
+
- An organization tried to nest workflow-level orchestration inside a step-level action
|
|
36
|
+
|
|
37
|
+
There is no workaround that preserves both the composite action wrapping and the reusable
|
|
38
|
+
workflow invocation. GitHub has confirmed this is by design and has no plans to change it.
|
|
39
|
+
The GitHub Actions team's rationale: reusable workflows operate at the job/runner level
|
|
40
|
+
and require job-level context (needs:, outputs:, secrets: inheritance) that cannot exist
|
|
41
|
+
inside a step.
|
|
42
|
+
fix: |
|
|
43
|
+
There is no fix — this is a platform limitation.
|
|
44
|
+
|
|
45
|
+
The recommended alternatives are:
|
|
46
|
+
1. **Restructure to call the reusable workflow at the job level** (not from inside a
|
|
47
|
+
composite action). Move the reusable workflow call to a dedicated job in your workflow
|
|
48
|
+
file using `jobs.my-job.uses:`.
|
|
49
|
+
2. **Convert the reusable workflow into a composite action** if the logic does not require
|
|
50
|
+
job-level features (matrix, needs: dependencies, explicit runner selection, secrets
|
|
51
|
+
inheritance via `secrets: inherit`).
|
|
52
|
+
3. **Inline the reusable workflow steps** into the composite action directly. While this
|
|
53
|
+
loses the DRY benefit of the reusable workflow, it resolves the structural constraint.
|
|
54
|
+
fix_code:
|
|
55
|
+
- language: yaml
|
|
56
|
+
label: "Call reusable workflow at job level (not from composite action)"
|
|
57
|
+
code: |
|
|
58
|
+
# ❌ This FAILS — cannot call reusable workflow from composite action step
|
|
59
|
+
# In: .github/actions/my-composite/action.yml
|
|
60
|
+
# runs:
|
|
61
|
+
# using: "composite"
|
|
62
|
+
# steps:
|
|
63
|
+
# - uses: ./.github/workflows/shared-build.yml # ERROR: not allowed
|
|
64
|
+
# with:
|
|
65
|
+
# environment: staging
|
|
66
|
+
|
|
67
|
+
# ✅ Call reusable workflow at job level in workflow file instead
|
|
68
|
+
jobs:
|
|
69
|
+
setup:
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
steps:
|
|
72
|
+
# Composite action can still do pre/post steps
|
|
73
|
+
- uses: ./.github/actions/my-composite
|
|
74
|
+
with:
|
|
75
|
+
param: value
|
|
76
|
+
|
|
77
|
+
shared-build:
|
|
78
|
+
needs: setup
|
|
79
|
+
uses: ./.github/workflows/shared-build.yml
|
|
80
|
+
with:
|
|
81
|
+
environment: staging
|
|
82
|
+
secrets: inherit
|
|
83
|
+
- language: yaml
|
|
84
|
+
label: "Convert reusable workflow to composite action (when job-level features not needed)"
|
|
85
|
+
code: |
|
|
86
|
+
# .github/actions/shared-build/action.yml
|
|
87
|
+
name: "Shared Build"
|
|
88
|
+
description: "Composite action equivalent of the shared-build reusable workflow"
|
|
89
|
+
inputs:
|
|
90
|
+
environment:
|
|
91
|
+
required: true
|
|
92
|
+
description: "Target environment"
|
|
93
|
+
runs:
|
|
94
|
+
using: "composite"
|
|
95
|
+
steps:
|
|
96
|
+
- name: Setup Node
|
|
97
|
+
uses: actions/setup-node@v4
|
|
98
|
+
with:
|
|
99
|
+
node-version: '20'
|
|
100
|
+
- name: Build
|
|
101
|
+
shell: bash
|
|
102
|
+
run: npm run build:${{ inputs.environment }}
|
|
103
|
+
prevention:
|
|
104
|
+
- "Understand the GitHub Actions hierarchy: Docker/JS/composite actions run WITHIN steps; reusable workflows run AS jobs."
|
|
105
|
+
- "Use composite actions when you want to share steps across workflows within a single job."
|
|
106
|
+
- "Use reusable workflows (workflow_call) when you want to share an entire job or multi-job sequence."
|
|
107
|
+
- "If you need both abstraction levels, combine them: a job calls a reusable workflow, and within that workflow's jobs, steps call composite actions."
|
|
108
|
+
docs:
|
|
109
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/reusing-workflows#limitations"
|
|
110
|
+
label: "GitHub docs — Reusing workflows: limitations (composite action restriction)"
|
|
111
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-composite-action"
|
|
112
|
+
label: "GitHub docs — Creating a composite action"
|
|
113
|
+
- url: "https://github.com/orgs/community/discussions/11834"
|
|
114
|
+
label: "Community discussion #11834 — reusable workflow called from composite action error"
|
|
115
|
+
- url: "https://stackoverflow.com/questions/70421268/how-to-reference-a-reusable-workflow-from-a-composite-action"
|
|
116
|
+
label: "Stack Overflow — reference reusable workflow from composite action"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
id: known-unsolved-015
|
|
2
|
+
title: "Scheduled Workflows Only Run on the Default Branch — Cannot Target Arbitrary Branches"
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- schedule
|
|
7
|
+
- cron
|
|
8
|
+
- default-branch
|
|
9
|
+
- trigger
|
|
10
|
+
- branches-filter
|
|
11
|
+
- platform-limitation
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "schedule.*branches|branches.*schedule"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "cron.*not.*branch|branch.*cron.*ignored"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "Unexpected value 'branches'"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "You have an error in your yaml syntax on line X"
|
|
21
|
+
- "Unexpected value 'branches'"
|
|
22
|
+
- "The 'branches' filter is not supported under 'schedule'"
|
|
23
|
+
root_cause: |
|
|
24
|
+
GitHub Actions schedule (cron) triggers **always run the workflow file from the
|
|
25
|
+
repository's default branch** (typically `main`). This is an intentional platform
|
|
26
|
+
security constraint.
|
|
27
|
+
|
|
28
|
+
There is no supported way to target a non-default branch with a `schedule:` trigger:
|
|
29
|
+
- Adding `branches:` under `schedule:` causes a YAML syntax error ("Unexpected value
|
|
30
|
+
'branches'") — it is not a valid key in that position.
|
|
31
|
+
- Placing the schedule workflow on a non-default branch does not cause it to run.
|
|
32
|
+
- The `github.ref` and `github.head_ref` values will always reflect the default branch
|
|
33
|
+
when triggered by a schedule event.
|
|
34
|
+
|
|
35
|
+
**Why GitHub enforces this:**
|
|
36
|
+
If scheduled workflows could target arbitrary branches, an attacker who pushes
|
|
37
|
+
malicious code to a feature branch could have it execute on a cron schedule with the
|
|
38
|
+
repository's full secrets — a significant supply-chain risk. By restricting cron runs
|
|
39
|
+
to the default branch, GitHub ensures only reviewed/merged code runs on schedule.
|
|
40
|
+
|
|
41
|
+
**Platform impact:**
|
|
42
|
+
- Cannot create separate nightly build schedules for multiple branches from one repo.
|
|
43
|
+
- Cannot run a scheduled integration test on a long-lived release branch without
|
|
44
|
+
special workarounds.
|
|
45
|
+
- Community has requested this capability since 2021 (community discussion #16107).
|
|
46
|
+
|
|
47
|
+
Sources: Stack Overflow Q/A, GitHub Community #16107, GitHub Docs
|
|
48
|
+
fix: |
|
|
49
|
+
There is no direct fix — this is a platform constraint. Use these workarounds:
|
|
50
|
+
|
|
51
|
+
**Workaround 1 (Recommended): Check out the target branch in the scheduled workflow**
|
|
52
|
+
Keep the scheduled workflow on the default branch but have the job check out the
|
|
53
|
+
desired branch in its `actions/checkout` step:
|
|
54
|
+
|
|
55
|
+
**Workaround 2: Trigger via `workflow_dispatch` from an external scheduler**
|
|
56
|
+
Use the GitHub REST API or `gh workflow run` from a controlled external system
|
|
57
|
+
(another scheduled job, GitHub App, etc.) to trigger a `workflow_dispatch` event
|
|
58
|
+
targeting a specific `ref` (branch or tag).
|
|
59
|
+
|
|
60
|
+
**Workaround 3: `workflow_run` chaining**
|
|
61
|
+
Not a direct substitute, but a workflow triggered by `workflow_run` on a push to
|
|
62
|
+
the target branch can perform scheduled-like follow-up logic.
|
|
63
|
+
|
|
64
|
+
**For release branches:** Merge a scheduled workflow file into each release branch
|
|
65
|
+
as part of the branch maintenance process — each branch can independently schedule
|
|
66
|
+
its own cron.
|
|
67
|
+
fix_code:
|
|
68
|
+
- language: yaml
|
|
69
|
+
label: "Broken — branches filter not valid under schedule, causes YAML error"
|
|
70
|
+
code: |
|
|
71
|
+
# ❌ BROKEN: 'branches' is not a valid key under schedule — YAML syntax error
|
|
72
|
+
on:
|
|
73
|
+
schedule:
|
|
74
|
+
- cron: '0 1 * * *'
|
|
75
|
+
branches: # ← invalid key, causes: Unexpected value 'branches'
|
|
76
|
+
- release/v2
|
|
77
|
+
- language: yaml
|
|
78
|
+
label: "Workaround — check out target branch in the scheduled workflow step"
|
|
79
|
+
code: |
|
|
80
|
+
# ✅ WORKAROUND: workflow lives on default branch, checks out the desired ref
|
|
81
|
+
on:
|
|
82
|
+
schedule:
|
|
83
|
+
- cron: '0 1 * * *' # runs from default branch (main)
|
|
84
|
+
|
|
85
|
+
jobs:
|
|
86
|
+
nightly-test:
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
steps:
|
|
89
|
+
- uses: actions/checkout@v4
|
|
90
|
+
with:
|
|
91
|
+
ref: 'release/v2' # explicitly check out the target branch
|
|
92
|
+
# All subsequent steps operate on release/v2 code
|
|
93
|
+
- run: ./run-tests.sh
|
|
94
|
+
- language: yaml
|
|
95
|
+
label: "Workaround — trigger workflow_dispatch against a specific branch via gh CLI"
|
|
96
|
+
code: |
|
|
97
|
+
# ✅ WORKAROUND: External script that fires workflow_dispatch targeting a branch
|
|
98
|
+
# Run this from another scheduled job (e.g., a GitHub App or a cron runner)
|
|
99
|
+
gh workflow run nightly.yml \
|
|
100
|
+
--repo owner/repo \
|
|
101
|
+
--ref release/v2 # workflow_dispatch supports specifying a branch ref
|
|
102
|
+
prevention:
|
|
103
|
+
- "Do not add `branches:` under `schedule:` — it is not a valid key and will cause YAML syntax errors."
|
|
104
|
+
- "Design scheduled workflows to check out the target branch explicitly via `actions/checkout` with the `ref:` input."
|
|
105
|
+
- "For complex multi-branch scheduling needs, consider using a GitHub App or external scheduler that fires `workflow_dispatch` events targeting specific refs."
|
|
106
|
+
- "Document this platform limitation in team wikis to prevent repeated attempts at adding `branches:` to schedule triggers."
|
|
107
|
+
docs:
|
|
108
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#schedule"
|
|
109
|
+
label: "GitHub Docs: Events that trigger workflows — schedule"
|
|
110
|
+
- url: "https://github.com/orgs/community/discussions/16107"
|
|
111
|
+
label: "GitHub Community #16107 — Schedule trigger target branch request"
|
|
112
|
+
- url: "https://stackoverflow.com/questions/63612155/how-do-i-trigger-a-scheduled-action-on-a-specific-branch"
|
|
113
|
+
label: "Stack Overflow: How to trigger a scheduled action on a specific branch"
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
id: known-unsolved-016
|
|
2
|
+
title: "secrets Context Cannot Be Directly Referenced in if: Conditions"
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- secrets
|
|
7
|
+
- if-condition
|
|
8
|
+
- conditional
|
|
9
|
+
- env
|
|
10
|
+
- limitation
|
|
11
|
+
- expressions
|
|
12
|
+
- step-condition
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: "if:.*\\$\\{\\{.*secrets\\..*\\}\\}"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "secrets\\.[A-Z_a-z]+\\s*!=\\s*''"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "This job was skipped."
|
|
20
|
+
root_cause: |
|
|
21
|
+
GitHub Actions explicitly prohibits direct use of the `secrets` context in `if:`
|
|
22
|
+
conditional expressions at the job or step level. The platform blocks this as a
|
|
23
|
+
security measure to prevent secret values from being leaked through expression
|
|
24
|
+
evaluation in workflow logs.
|
|
25
|
+
|
|
26
|
+
Attempting `if: ${{ secrets.MY_SECRET != '' }}` or `if: secrets.MY_SECRET` silently
|
|
27
|
+
fails — the condition evaluates to an empty string (falsy), causing the step or job
|
|
28
|
+
to be skipped with no error message, warning, or explanation.
|
|
29
|
+
|
|
30
|
+
The same restriction applies at the job level:
|
|
31
|
+
```yaml
|
|
32
|
+
jobs:
|
|
33
|
+
deploy:
|
|
34
|
+
if: ${{ secrets.DEPLOY_KEY != '' }} # silently evaluates wrong
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This catches developers off guard because:
|
|
38
|
+
- The workflow is syntactically valid and passes YAML linting — no parse error
|
|
39
|
+
- The failure mode is silent: the job/step is skipped with "This job was skipped"
|
|
40
|
+
rather than an explicit error about the secrets restriction
|
|
41
|
+
- The pattern looks correct by analogy with other contexts (`github.*`, `env.*`,
|
|
42
|
+
`vars.*`) that DO work in `if:` conditions
|
|
43
|
+
- Secrets used in `run:` scripts and `with:` inputs work fine — only `if:` is blocked
|
|
44
|
+
|
|
45
|
+
GitHub's documentation explicitly states: "Secrets cannot be directly referenced
|
|
46
|
+
in `if:` conditionals. Instead, consider setting secrets as job-level environment
|
|
47
|
+
variables, then referencing the environment variables to conditionally run steps."
|
|
48
|
+
|
|
49
|
+
As of 2026 this is a by-design limitation with no planned native fix.
|
|
50
|
+
`actionlint` now flags direct secret references in `if:` expressions as an error.
|
|
51
|
+
|
|
52
|
+
Sources:
|
|
53
|
+
- GitHub Docs — Using secrets in GitHub Actions (use-secrets.md)
|
|
54
|
+
- github/docs#12722 — PR documenting the `if:` condition restriction
|
|
55
|
+
fix: |
|
|
56
|
+
Promote the secret to a job-level `env:` variable that evaluates the boolean (not the
|
|
57
|
+
secret value itself), then reference `env.VARIABLE_NAME` in the `if:` condition.
|
|
58
|
+
|
|
59
|
+
For job-level `if:` (not step-level), use a preceding detection job that outputs
|
|
60
|
+
whether the secret is present, and condition the downstream job on that output.
|
|
61
|
+
fix_code:
|
|
62
|
+
- language: yaml
|
|
63
|
+
label: "WRONG — direct secrets reference in step if: condition"
|
|
64
|
+
code: |
|
|
65
|
+
jobs:
|
|
66
|
+
deploy:
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
steps:
|
|
69
|
+
- name: Deploy to production
|
|
70
|
+
if: ${{ secrets.DEPLOY_KEY != '' }} # silently wrong — step is skipped
|
|
71
|
+
run: ./deploy.sh
|
|
72
|
+
env:
|
|
73
|
+
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
|
|
74
|
+
|
|
75
|
+
- language: yaml
|
|
76
|
+
label: "CORRECT — promote secret presence to job-level env, reference in step if:"
|
|
77
|
+
code: |
|
|
78
|
+
jobs:
|
|
79
|
+
deploy:
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
env:
|
|
82
|
+
# Safe: evaluates to "true"/"false" string — not the secret value itself
|
|
83
|
+
HAS_DEPLOY_KEY: ${{ secrets.DEPLOY_KEY != '' }}
|
|
84
|
+
steps:
|
|
85
|
+
- name: Deploy to production
|
|
86
|
+
if: env.HAS_DEPLOY_KEY == 'true'
|
|
87
|
+
run: ./deploy.sh
|
|
88
|
+
env:
|
|
89
|
+
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
|
|
90
|
+
|
|
91
|
+
- language: yaml
|
|
92
|
+
label: "CORRECT — job-level conditional via detection job + output"
|
|
93
|
+
code: |
|
|
94
|
+
jobs:
|
|
95
|
+
check-secrets:
|
|
96
|
+
runs-on: ubuntu-latest
|
|
97
|
+
outputs:
|
|
98
|
+
has_key: ${{ steps.check.outputs.has_key }}
|
|
99
|
+
steps:
|
|
100
|
+
- id: check
|
|
101
|
+
env:
|
|
102
|
+
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
|
|
103
|
+
run: |
|
|
104
|
+
if [ -n "$DEPLOY_KEY" ]; then
|
|
105
|
+
echo "has_key=true" >> "$GITHUB_OUTPUT"
|
|
106
|
+
else
|
|
107
|
+
echo "has_key=false" >> "$GITHUB_OUTPUT"
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
deploy:
|
|
111
|
+
needs: check-secrets
|
|
112
|
+
# Job-level if: can reference job outputs — not secrets directly
|
|
113
|
+
if: needs.check-secrets.outputs.has_key == 'true'
|
|
114
|
+
runs-on: ubuntu-latest
|
|
115
|
+
steps:
|
|
116
|
+
- run: ./deploy.sh
|
|
117
|
+
env:
|
|
118
|
+
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
|
|
119
|
+
|
|
120
|
+
- language: yaml
|
|
121
|
+
label: "CORRECT — inline env check using bash within step"
|
|
122
|
+
code: |
|
|
123
|
+
jobs:
|
|
124
|
+
deploy:
|
|
125
|
+
runs-on: ubuntu-latest
|
|
126
|
+
steps:
|
|
127
|
+
- name: Deploy (skip gracefully if secret absent)
|
|
128
|
+
env:
|
|
129
|
+
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
|
|
130
|
+
run: |
|
|
131
|
+
if [ -z "$DEPLOY_KEY" ]; then
|
|
132
|
+
echo "No DEPLOY_KEY configured — skipping deployment"
|
|
133
|
+
exit 0
|
|
134
|
+
fi
|
|
135
|
+
./deploy.sh
|
|
136
|
+
prevention:
|
|
137
|
+
- "Never reference `secrets.*` directly in `if:` conditions — always promote to `env:` first."
|
|
138
|
+
- "Use `env.HAS_SECRET: ${{ secrets.MY_SECRET != '' }}` at the job level to create a safe boolean env var."
|
|
139
|
+
- "Install and run `actionlint` in CI — it now flags direct secret references in `if:` as an error."
|
|
140
|
+
- "For job-level conditionals based on secret presence, use a dedicated detection job with outputs."
|
|
141
|
+
docs:
|
|
142
|
+
- url: "https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#using-secrets-in-a-workflow"
|
|
143
|
+
label: "GitHub Docs: Using secrets in a workflow (secrets cannot be in if: conditionals)"
|
|
144
|
+
- url: "https://github.com/github/docs/pull/12722"
|
|
145
|
+
label: "github/docs#12722 — Document secrets in if: conditionals limitation"
|
|
146
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsif"
|
|
147
|
+
label: "Workflow syntax: steps[*].if"
|
|
148
|
+
- url: "https://github.com/rhysd/actionlint"
|
|
149
|
+
label: "actionlint — flags direct secrets in if: conditions as an error"
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
id: permissions-auth-013
|
|
2
|
+
title: "Dependabot Pull Requests Cannot Access Repository Secrets — Read-Only Token"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- dependabot
|
|
7
|
+
- secrets
|
|
8
|
+
- github-token
|
|
9
|
+
- pull-request
|
|
10
|
+
- read-only
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "Context access might be invalid: secrets\\."
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "denied|forbidden|403"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "Resource not accessible by integration"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "Context access might be invalid: secrets.NPM_TOKEN"
|
|
20
|
+
- "Error: Process completed with exit code 1."
|
|
21
|
+
- "HttpError: Resource not accessible by integration"
|
|
22
|
+
root_cause: |
|
|
23
|
+
For security reasons, GitHub Actions workflows triggered by Dependabot pull requests
|
|
24
|
+
are granted a **read-only GITHUB_TOKEN** and **cannot access repository secrets**.
|
|
25
|
+
|
|
26
|
+
When a Dependabot PR triggers an `on: pull_request` workflow:
|
|
27
|
+
- `secrets.*` expressions evaluate to empty string (not an error — silently empty)
|
|
28
|
+
- `GITHUB_TOKEN` has read-only permissions (cannot write to the repository, push
|
|
29
|
+
tags, create releases, comment on PRs, etc.)
|
|
30
|
+
- Steps that need secrets (publishing to npm, Docker Hub, PyPI, etc.) fail with
|
|
31
|
+
authentication errors or silently produce wrong results
|
|
32
|
+
|
|
33
|
+
This is intentional: Dependabot opens PRs that update dependencies from external
|
|
34
|
+
sources. If Dependabot-triggered workflows had full secret access, a malicious
|
|
35
|
+
package update could exfiltrate your credentials via a modified build step.
|
|
36
|
+
|
|
37
|
+
The failure is often silent because:
|
|
38
|
+
- The secret evaluates to empty string — tools may fail with "missing credentials"
|
|
39
|
+
rather than a clear "secret unavailable" error
|
|
40
|
+
- `GITHUB_TOKEN` write failures show as 403 "Resource not accessible by integration"
|
|
41
|
+
|
|
42
|
+
Sources: dependabot/dependabot-core#3253, #5464
|
|
43
|
+
fix: |
|
|
44
|
+
Choose one of these approaches depending on your security requirements:
|
|
45
|
+
|
|
46
|
+
**Option 1 (Recommended): Use Dependabot secrets**
|
|
47
|
+
Add the required secrets to the Dependabot secrets store (Settings > Secrets and
|
|
48
|
+
variables > Dependabot). Dependabot-triggered workflows can access these separately
|
|
49
|
+
from repository secrets.
|
|
50
|
+
|
|
51
|
+
**Option 2: Auto-approve and merge without secrets**
|
|
52
|
+
For dependency updates, use `pull_request_review` + `pull_request` auto-merge
|
|
53
|
+
pattern — approve Dependabot PRs that pass CI without needing secrets.
|
|
54
|
+
|
|
55
|
+
**Option 3: Use `pull_request_target` with caution**
|
|
56
|
+
`pull_request_target` runs in the base repo context and has full secret access.
|
|
57
|
+
Only use this for trusted, controlled steps (like leaving a comment) — never
|
|
58
|
+
checkout and run untrusted fork code in a `pull_request_target` step with secrets.
|
|
59
|
+
|
|
60
|
+
**Option 4: Filter out Dependabot runs**
|
|
61
|
+
Add `if: github.actor != 'dependabot[bot]'` to skip secret-requiring steps for
|
|
62
|
+
Dependabot PRs, letting CI pass without the publishing step.
|
|
63
|
+
fix_code:
|
|
64
|
+
- language: yaml
|
|
65
|
+
label: "Add secrets to Dependabot secrets store (Settings → Secrets → Dependabot)"
|
|
66
|
+
code: |
|
|
67
|
+
# Add NPM_TOKEN (and any other required secrets) to:
|
|
68
|
+
# Settings > Secrets and variables > Dependabot
|
|
69
|
+
# These are separate from repository secrets and accessible to Dependabot runs.
|
|
70
|
+
|
|
71
|
+
# No workflow change needed — ${{ secrets.NPM_TOKEN }} will resolve correctly
|
|
72
|
+
# once the secret is added to the Dependabot secrets store.
|
|
73
|
+
- language: yaml
|
|
74
|
+
label: "Skip secret-requiring steps for Dependabot PRs"
|
|
75
|
+
code: |
|
|
76
|
+
jobs:
|
|
77
|
+
build:
|
|
78
|
+
runs-on: ubuntu-latest
|
|
79
|
+
steps:
|
|
80
|
+
- uses: actions/checkout@v4
|
|
81
|
+
|
|
82
|
+
- name: Run tests
|
|
83
|
+
run: npm test
|
|
84
|
+
# Tests run for all PRs including Dependabot
|
|
85
|
+
|
|
86
|
+
- name: Publish to npm (skip for Dependabot)
|
|
87
|
+
if: github.actor != 'dependabot[bot]'
|
|
88
|
+
run: npm publish
|
|
89
|
+
env:
|
|
90
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
91
|
+
# NPM_TOKEN is empty for Dependabot — skip the step entirely
|
|
92
|
+
- language: yaml
|
|
93
|
+
label: "Auto-approve Dependabot PRs using Dependabot secrets + gh CLI"
|
|
94
|
+
code: |
|
|
95
|
+
name: Auto-approve Dependabot
|
|
96
|
+
on: pull_request
|
|
97
|
+
|
|
98
|
+
permissions:
|
|
99
|
+
pull-requests: write
|
|
100
|
+
contents: write
|
|
101
|
+
|
|
102
|
+
jobs:
|
|
103
|
+
auto-approve:
|
|
104
|
+
runs-on: ubuntu-latest
|
|
105
|
+
if: github.actor == 'dependabot[bot]'
|
|
106
|
+
steps:
|
|
107
|
+
- name: Fetch Dependabot metadata
|
|
108
|
+
id: meta
|
|
109
|
+
uses: dependabot/fetch-metadata@v2
|
|
110
|
+
|
|
111
|
+
- name: Auto-approve minor and patch updates
|
|
112
|
+
if: steps.meta.outputs.update-type != 'version-update:semver-major'
|
|
113
|
+
run: gh pr review --approve "$PR_URL"
|
|
114
|
+
env:
|
|
115
|
+
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
116
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
117
|
+
# GITHUB_TOKEN in dependabot context is read-only for repo content
|
|
118
|
+
# but CAN approve PRs when permissions: pull-requests: write is set
|
|
119
|
+
prevention:
|
|
120
|
+
- "Never assume repository secrets are available in Dependabot-triggered workflows — they aren't."
|
|
121
|
+
- "Use `github.actor == 'dependabot[bot]'` conditions to branch logic for Dependabot vs human PRs."
|
|
122
|
+
- "Add required secrets to the Dependabot secrets store separately from repository secrets."
|
|
123
|
+
- "Audit CI workflows for `${{ secrets.* }}` usage and test with a Dependabot PR to confirm behavior."
|
|
124
|
+
- "Do not use `pull_request_target` with `actions/checkout` on the PR head ref — this is a security vulnerability."
|
|
125
|
+
docs:
|
|
126
|
+
- url: "https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions"
|
|
127
|
+
label: "Automating Dependabot with GitHub Actions"
|
|
128
|
+
- url: "https://docs.github.com/en/code-security/dependabot/working-with-dependabot/configuring-access-to-private-registries-for-dependabot"
|
|
129
|
+
label: "Configuring Dependabot secrets"
|
|
130
|
+
- url: "https://github.com/dependabot/dependabot-core/issues/3253"
|
|
131
|
+
label: "dependabot-core#3253 — secrets unavailable in Dependabot-triggered workflows"
|
|
132
|
+
- url: "https://github.com/dependabot/dependabot-core/issues/5464"
|
|
133
|
+
label: "dependabot-core#5464 — Dependabot read-only token behavior"
|