@htekdev/actions-debugger 1.0.78 → 1.0.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/errors/caching-artifacts/cache-exact-key-hit-skips-save-stale.yml +81 -0
- package/errors/caching-artifacts/merge-queue-branch-cache-isolation.yml +80 -0
- package/errors/concurrency-timing/rerun-cancel-original-missing-run-attempt.yml +67 -0
- package/errors/concurrency-timing/workflow-dispatch-push-shared-concurrency-cancel.yml +64 -0
- package/errors/permissions-auth/dependabot-pr-repository-secrets-not-injected.yml +81 -0
- package/errors/silent-failures/fromjson-object-stored-in-env-stringifies.yml +77 -0
- package/errors/triggers/event-types-unknown-value-silently-ignored.yml +89 -0
- package/errors/yaml-syntax/secrets-context-unavailable-in-if-condition.yml +72 -0
- package/package.json +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
id: caching-artifacts-046
|
|
2
|
+
title: "actions/cache exact key hit skips save step — caches go stale when dependency hash doesn't change"
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- save-always
|
|
8
|
+
- cache-hit
|
|
9
|
+
- stale-cache
|
|
10
|
+
- cache-invalidation
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'Cache hit occurred on the primary key'
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: 'Not saving cache'
|
|
15
|
+
flags: i
|
|
16
|
+
error_messages:
|
|
17
|
+
- "Cache hit occurred on the primary key, not saving cache."
|
|
18
|
+
- "Not saving cache as it is not requested."
|
|
19
|
+
root_cause: |
|
|
20
|
+
When actions/cache finds an exact match for the primary key, it sets
|
|
21
|
+
cache-hit: 'true' and the post-step save is skipped entirely. The rationale is
|
|
22
|
+
that since the key already exists, re-saving an identical entry would be wasteful.
|
|
23
|
+
|
|
24
|
+
However, this behaviour means that cache content changes that do NOT affect the
|
|
25
|
+
hash (e.g., transitive package security patches, tool binary updates, or
|
|
26
|
+
generated files included in the cached path) are never persisted. The cache
|
|
27
|
+
entry stays frozen at the state it was in when the key was first saved.
|
|
28
|
+
|
|
29
|
+
Common scenario: a workflow caches node_modules keyed on package-lock.json hash.
|
|
30
|
+
The lock file goes unchanged for weeks, but npm packages receive silent patch
|
|
31
|
+
updates or a cached binary becomes corrupt. Every run gets a cache hit, skips the
|
|
32
|
+
save, and the stale or corrupt content continues to be served until the lock file
|
|
33
|
+
hash finally changes.
|
|
34
|
+
fix: |
|
|
35
|
+
Set save-always: true on the cache step to force a save even when the exact key
|
|
36
|
+
already exists. For finer control, split the action into separate
|
|
37
|
+
actions/cache/restore and actions/cache/save steps so the save can run
|
|
38
|
+
conditionally. To immediately invalidate a specific cache entry, delete it via
|
|
39
|
+
the GitHub REST API or the Actions UI under Repository > Actions > Caches.
|
|
40
|
+
fix_code:
|
|
41
|
+
- language: yaml
|
|
42
|
+
label: "Force save on every run with save-always"
|
|
43
|
+
code: |
|
|
44
|
+
- name: Cache node_modules
|
|
45
|
+
uses: actions/cache@v4
|
|
46
|
+
with:
|
|
47
|
+
path: node_modules
|
|
48
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
49
|
+
restore-keys: ${{ runner.os }}-node-
|
|
50
|
+
# Always save even if exact key already exists in the cache
|
|
51
|
+
save-always: true
|
|
52
|
+
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: "Split restore and save for conditional save logic"
|
|
55
|
+
code: |
|
|
56
|
+
- name: Restore cache
|
|
57
|
+
id: cache-restore
|
|
58
|
+
uses: actions/cache/restore@v4
|
|
59
|
+
with:
|
|
60
|
+
path: node_modules
|
|
61
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
62
|
+
restore-keys: ${{ runner.os }}-node-
|
|
63
|
+
|
|
64
|
+
- name: Install dependencies
|
|
65
|
+
run: npm ci
|
|
66
|
+
|
|
67
|
+
- name: Save cache
|
|
68
|
+
uses: actions/cache/save@v4
|
|
69
|
+
if: always()
|
|
70
|
+
with:
|
|
71
|
+
path: node_modules
|
|
72
|
+
key: ${{ steps.cache-restore.outputs.cache-primary-key }}
|
|
73
|
+
prevention:
|
|
74
|
+
- "Use save-always: true when cached content may diverge from the hash key (e.g., tool caches, binary downloads)."
|
|
75
|
+
- "Periodically evict stale cache entries via the GitHub REST API DELETE /repos/{owner}/{repo}/actions/caches/{cache_id}."
|
|
76
|
+
- "Include a tool version or date component in the cache key when the content should rotate on a schedule."
|
|
77
|
+
docs:
|
|
78
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows"
|
|
79
|
+
label: "GitHub Actions: Caching dependencies"
|
|
80
|
+
- url: "https://github.com/actions/cache#save-always"
|
|
81
|
+
label: "actions/cache README: save-always option"
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
id: caching-artifacts-045
|
|
2
|
+
title: "Merge queue (merge_group) runs cannot restore cache saved by pull_request runs"
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- merge-queue
|
|
7
|
+
- merge_group
|
|
8
|
+
- cache-isolation
|
|
9
|
+
- branch-scoping
|
|
10
|
+
- cache-miss
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'Cache not found for input keys'
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: 'gh-readonly-queue'
|
|
15
|
+
flags: i
|
|
16
|
+
error_messages:
|
|
17
|
+
- "Cache not found for input keys:"
|
|
18
|
+
- "No cache found for key: Linux-node-"
|
|
19
|
+
root_cause: |
|
|
20
|
+
GitHub Actions caches are branch-scoped. When the merge_group trigger fires,
|
|
21
|
+
GitHub creates a temporary branch named
|
|
22
|
+
gh-readonly-queue/main/pr-<number>-<sha> with a fresh merge commit. This
|
|
23
|
+
temporary branch has no prior cache entries, so every cache lookup is a cache
|
|
24
|
+
miss — even if the identical hashFiles() key was successfully saved during the
|
|
25
|
+
preceding pull_request run on the feature branch.
|
|
26
|
+
|
|
27
|
+
Branch-scoped cache fallback rules allow access to the base branch (e.g., main)
|
|
28
|
+
but NOT to arbitrary feature branches. The gh-readonly-queue/* branch is treated
|
|
29
|
+
as a new branch with no cache history and no access to feature-branch caches.
|
|
30
|
+
Only caches saved directly to main (or the configured merge target) are accessible
|
|
31
|
+
in merge queue runs.
|
|
32
|
+
|
|
33
|
+
The result: CI times effectively double. The pull_request run warms the cache
|
|
34
|
+
but the merge queue run cannot use it and must rebuild from scratch.
|
|
35
|
+
fix: |
|
|
36
|
+
Seed caches on the base branch via push-triggered or scheduled workflows so
|
|
37
|
+
merge queue runs can restore from main's cache. Use restore-keys with a
|
|
38
|
+
branch-agnostic prefix to maximize fallback hit rate.
|
|
39
|
+
fix_code:
|
|
40
|
+
- language: yaml
|
|
41
|
+
label: "Use restore-keys for base branch fallback"
|
|
42
|
+
code: |
|
|
43
|
+
- name: Cache dependencies
|
|
44
|
+
uses: actions/cache@v4
|
|
45
|
+
with:
|
|
46
|
+
path: ~/.npm
|
|
47
|
+
# Exact key includes lock file hash — works for push/PR
|
|
48
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
49
|
+
# restore-keys fall back to base branch cache (accessible in merge_group)
|
|
50
|
+
restore-keys: |
|
|
51
|
+
${{ runner.os }}-node-
|
|
52
|
+
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: "Seed base branch cache via push workflow"
|
|
55
|
+
code: |
|
|
56
|
+
# Separate workflow to warm cache on main after every merge
|
|
57
|
+
on:
|
|
58
|
+
push:
|
|
59
|
+
branches: [main]
|
|
60
|
+
|
|
61
|
+
jobs:
|
|
62
|
+
warm-cache:
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v4
|
|
66
|
+
- uses: actions/setup-node@v4
|
|
67
|
+
with:
|
|
68
|
+
node-version: '20'
|
|
69
|
+
cache: npm
|
|
70
|
+
- run: npm ci
|
|
71
|
+
prevention:
|
|
72
|
+
- "Seed caches on your base branch (main) so merge queue runs can restore from it."
|
|
73
|
+
- "Use branch-agnostic restore-keys prefixes — they survive the gh-readonly-queue/* branch context."
|
|
74
|
+
- "Avoid cache keys that include branch name or github.head_ref — they will never match in merge queue."
|
|
75
|
+
- "setup-node/setup-python cache: integration automatically includes restore-keys and handles this gracefully."
|
|
76
|
+
docs:
|
|
77
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#merge_group"
|
|
78
|
+
label: "GitHub Actions: merge_group trigger"
|
|
79
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache"
|
|
80
|
+
label: "GitHub Actions: Cache branch access restrictions"
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
id: concurrency-timing-039
|
|
2
|
+
title: "Manual re-run cancels original run when concurrency group omits github.run_attempt"
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- concurrency
|
|
7
|
+
- re-run
|
|
8
|
+
- cancel-in-progress
|
|
9
|
+
- run-attempt
|
|
10
|
+
- manual-rerun
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'This run was cancelled'
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: 'run_attempt'
|
|
15
|
+
flags: i
|
|
16
|
+
error_messages:
|
|
17
|
+
- "This run was cancelled."
|
|
18
|
+
- "A run for this workflow is already in progress."
|
|
19
|
+
root_cause: |
|
|
20
|
+
When cancel-in-progress: true is set and the concurrency group key does not
|
|
21
|
+
include github.run_attempt, every manual re-run of a workflow is treated as a
|
|
22
|
+
duplicate of the original run (since run_id and ref are the same). The re-run
|
|
23
|
+
immediately cancels the still-executing original attempt, or the concurrency
|
|
24
|
+
group queues the re-run and cancels any pending run — which may be the active
|
|
25
|
+
original.
|
|
26
|
+
|
|
27
|
+
The run_id is stable across re-runs but run_attempt increments (1, 2, 3...).
|
|
28
|
+
Without run_attempt in the concurrency group key, GitHub has no way to
|
|
29
|
+
distinguish re-run attempt 2 from the still-running attempt 1.
|
|
30
|
+
|
|
31
|
+
This produces confusing CI failures: a developer clicks "Re-run jobs" on a
|
|
32
|
+
failed workflow and immediately sees the original (or the re-run itself)
|
|
33
|
+
cancelled by the concurrency system.
|
|
34
|
+
fix: |
|
|
35
|
+
Include github.run_attempt in the concurrency group key so each attempt is
|
|
36
|
+
treated as a distinct slot. This allows re-runs to coexist with or cleanly
|
|
37
|
+
supersede the original without unexpected cancellations.
|
|
38
|
+
fix_code:
|
|
39
|
+
- language: yaml
|
|
40
|
+
label: "Include run_attempt in concurrency group key"
|
|
41
|
+
code: |
|
|
42
|
+
# WRONG: re-runs and originals share the same concurrency slot
|
|
43
|
+
# concurrency:
|
|
44
|
+
# group: ${{ github.workflow }}-${{ github.ref }}
|
|
45
|
+
# cancel-in-progress: true
|
|
46
|
+
|
|
47
|
+
# CORRECT: each attempt gets its own slot, preventing cross-attempt cancellation
|
|
48
|
+
concurrency:
|
|
49
|
+
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.run_attempt }}
|
|
50
|
+
cancel-in-progress: true
|
|
51
|
+
|
|
52
|
+
- language: yaml
|
|
53
|
+
label: "Alternative: cancel-in-progress only for push/PR, not re-runs"
|
|
54
|
+
code: |
|
|
55
|
+
concurrency:
|
|
56
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
57
|
+
# Only cancel-in-progress for first attempt; re-runs are never cancelled
|
|
58
|
+
cancel-in-progress: ${{ github.run_attempt == 1 }}
|
|
59
|
+
prevention:
|
|
60
|
+
- "Always include github.run_attempt in concurrency group keys when cancel-in-progress: true is set."
|
|
61
|
+
- "Test re-run behavior explicitly — concurrency cancellation bugs only surface when you manually re-run."
|
|
62
|
+
- "Consider using cancel-in-progress: ${{ github.run_attempt == 1 }} to preserve re-run integrity."
|
|
63
|
+
docs:
|
|
64
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#concurrency"
|
|
65
|
+
label: "GitHub Actions: Concurrency syntax"
|
|
66
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#github-context"
|
|
67
|
+
label: "GitHub Actions: github.run_attempt context"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
id: concurrency-timing-040
|
|
2
|
+
title: "workflow_dispatch and push share concurrency group, dispatch cancels in-progress CI"
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- concurrency
|
|
7
|
+
- workflow-dispatch
|
|
8
|
+
- push
|
|
9
|
+
- cancel-in-progress
|
|
10
|
+
- event-name
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'This run was cancelled'
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: 'concurrency.*workflow_dispatch|workflow_dispatch.*concurrency'
|
|
15
|
+
flags: ims
|
|
16
|
+
error_messages:
|
|
17
|
+
- "This run was cancelled."
|
|
18
|
+
- "A run for this workflow is already in progress."
|
|
19
|
+
root_cause: |
|
|
20
|
+
When a concurrency group key is built from github.workflow and github.ref alone,
|
|
21
|
+
both workflow_dispatch and push events on the same branch produce an identical key.
|
|
22
|
+
GitHub treats them as competing occupants of the same concurrency slot.
|
|
23
|
+
|
|
24
|
+
With cancel-in-progress: true, a manually dispatched run immediately cancels any
|
|
25
|
+
push-triggered CI run already in progress (or vice versa). Without cancel-in-progress,
|
|
26
|
+
the dispatch run queues behind the push run but still competes for the same slot.
|
|
27
|
+
|
|
28
|
+
This catches developers off guard when they manually trigger a workflow for
|
|
29
|
+
debugging while CI is running — the in-progress CI run is silently cancelled. Or a
|
|
30
|
+
push to the same branch during a long dispatch run causes the dispatch to be evicted.
|
|
31
|
+
fix: |
|
|
32
|
+
Include github.event_name in the concurrency group key to give workflow_dispatch
|
|
33
|
+
and push events separate concurrency slots. Alternatively, use a conditional
|
|
34
|
+
cancel-in-progress expression so manual dispatches are never cancelled.
|
|
35
|
+
fix_code:
|
|
36
|
+
- language: yaml
|
|
37
|
+
label: "Include event_name to separate dispatch and push slots"
|
|
38
|
+
code: |
|
|
39
|
+
# WRONG: dispatch and push on same branch share a slot and cancel each other
|
|
40
|
+
# concurrency:
|
|
41
|
+
# group: ${{ github.workflow }}-${{ github.ref }}
|
|
42
|
+
# cancel-in-progress: true
|
|
43
|
+
|
|
44
|
+
# CORRECT: each event type gets its own concurrency slot
|
|
45
|
+
concurrency:
|
|
46
|
+
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
|
|
47
|
+
cancel-in-progress: true
|
|
48
|
+
|
|
49
|
+
- language: yaml
|
|
50
|
+
label: "Alternative: only cancel-in-progress for push and pull_request, not dispatch"
|
|
51
|
+
code: |
|
|
52
|
+
concurrency:
|
|
53
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
54
|
+
# Manual dispatches are never cancelled; push/PR runs are stacked-and-cancelled
|
|
55
|
+
cancel-in-progress: ${{ github.event_name != 'workflow_dispatch' }}
|
|
56
|
+
prevention:
|
|
57
|
+
- "Always include github.event_name in concurrency group keys when a workflow has multiple trigger events."
|
|
58
|
+
- "Test concurrency behavior by triggering both events in quick succession; bugs only surface under race conditions."
|
|
59
|
+
- "For release or deploy workflows, consider separate workflows for dispatch vs automated CI rather than sharing concurrency."
|
|
60
|
+
docs:
|
|
61
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#concurrency"
|
|
62
|
+
label: "GitHub Actions: Concurrency syntax"
|
|
63
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_dispatch"
|
|
64
|
+
label: "GitHub Actions: workflow_dispatch event"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
id: permissions-auth-048
|
|
2
|
+
title: "Dependabot PR workflows cannot access repository secrets — only Dependabot secrets are injected"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- dependabot
|
|
7
|
+
- secrets
|
|
8
|
+
- pull-request
|
|
9
|
+
- registry-auth
|
|
10
|
+
- secret-isolation
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'Input required and not supplied'
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: '401 Unauthorized'
|
|
15
|
+
flags: i
|
|
16
|
+
error_messages:
|
|
17
|
+
- "Error: Input required and not supplied: token"
|
|
18
|
+
- "npm ERR! code E401"
|
|
19
|
+
- "Error: Unauthorized"
|
|
20
|
+
root_cause: |
|
|
21
|
+
When Dependabot opens or updates a pull request, GitHub Actions workflows triggered
|
|
22
|
+
by that PR run in an isolated secret context. Repository secrets (defined under
|
|
23
|
+
Settings > Secrets and variables > Actions) are NOT injected into Dependabot-triggered
|
|
24
|
+
workflow runs. Only secrets defined under Settings > Secrets and variables > Dependabot
|
|
25
|
+
are available.
|
|
26
|
+
|
|
27
|
+
This means steps that reference secrets.NPM_TOKEN, secrets.DOCKER_PASSWORD,
|
|
28
|
+
secrets.SONAR_TOKEN, or any custom repository secret will receive an empty string.
|
|
29
|
+
Actions that validate their inputs (e.g., a custom action with required: true on a
|
|
30
|
+
token input) will fail with "Input required and not supplied". Registry steps will
|
|
31
|
+
fail with 401 Unauthorized.
|
|
32
|
+
|
|
33
|
+
GITHUB_TOKEN is exempt and is still injected normally for Dependabot runs.
|
|
34
|
+
fix: |
|
|
35
|
+
Re-create the required secrets under Settings > Secrets and variables > Dependabot
|
|
36
|
+
(a separate namespace from Actions secrets). Dependabot reads from its own secret
|
|
37
|
+
store, not the repository Actions secret store. Alternatively, restructure the
|
|
38
|
+
workflow so secret-dependent steps only run on non-Dependabot actors using an
|
|
39
|
+
if: condition.
|
|
40
|
+
fix_code:
|
|
41
|
+
- language: yaml
|
|
42
|
+
label: "Add secrets to Dependabot secret store in GitHub UI"
|
|
43
|
+
code: |
|
|
44
|
+
# In GitHub: Settings > Secrets and variables > Dependabot
|
|
45
|
+
# Add the same secret names you use in your Actions secrets.
|
|
46
|
+
# They are stored separately and only injected for Dependabot-triggered runs.
|
|
47
|
+
|
|
48
|
+
# No workflow YAML change required — just add the secret in the UI.
|
|
49
|
+
# Example workflow remains unchanged:
|
|
50
|
+
jobs:
|
|
51
|
+
build:
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v4
|
|
55
|
+
- run: npm publish
|
|
56
|
+
env:
|
|
57
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # Works once added to Dependabot secrets
|
|
58
|
+
|
|
59
|
+
- language: yaml
|
|
60
|
+
label: "Skip secret-dependent steps when actor is dependabot"
|
|
61
|
+
code: |
|
|
62
|
+
jobs:
|
|
63
|
+
build:
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
steps:
|
|
66
|
+
- uses: actions/checkout@v4
|
|
67
|
+
- name: Publish to registry
|
|
68
|
+
# Skip publish step for Dependabot PRs that lack the token
|
|
69
|
+
if: github.actor != 'dependabot[bot]'
|
|
70
|
+
run: npm publish
|
|
71
|
+
env:
|
|
72
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
73
|
+
prevention:
|
|
74
|
+
- "Maintain parallel secrets under both Actions and Dependabot secret stores for tokens Dependabot PRs need."
|
|
75
|
+
- "Audit workflows triggered by pull_request to identify steps using custom secrets — they silently fail for Dependabot."
|
|
76
|
+
- "Use github.actor != 'dependabot[bot]' guards on publish/deploy steps that Dependabot PRs should skip."
|
|
77
|
+
docs:
|
|
78
|
+
- url: "https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#accessing-secrets"
|
|
79
|
+
label: "GitHub Docs: Accessing secrets in Dependabot workflows"
|
|
80
|
+
- url: "https://docs.github.com/en/code-security/dependabot/working-with-dependabot/configuring-access-to-private-registries-for-dependabot"
|
|
81
|
+
label: "GitHub Docs: Dependabot and private registries"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
id: silent-failures-074
|
|
2
|
+
title: "fromJSON() object or array stored in env: block silently becomes '[object Object]'"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- fromjson
|
|
7
|
+
- env-context
|
|
8
|
+
- type-coercion
|
|
9
|
+
- json-parsing
|
|
10
|
+
- stringification
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'env\s*:.*\$\{\{\s*fromJSON\('
|
|
13
|
+
flags: ims
|
|
14
|
+
error_messages:
|
|
15
|
+
- "[object Object]"
|
|
16
|
+
- "Unexpected token 'o', \"[object O\"... is not valid JSON"
|
|
17
|
+
root_cause: |
|
|
18
|
+
The fromJSON() function in GitHub Actions expressions parses a JSON string
|
|
19
|
+
into a typed value — object, array, boolean, or number. However, when the
|
|
20
|
+
result is assigned to an env: variable, the Actions runner coerces the value
|
|
21
|
+
to a string using JavaScript's default toString(). For objects this produces
|
|
22
|
+
the literal string [object Object]; for arrays it produces a comma-joined
|
|
23
|
+
string (e.g., a,b,c).
|
|
24
|
+
|
|
25
|
+
The step exits with code 0 and the env variable appears set, but its value is
|
|
26
|
+
the stringified form rather than the JSON structure the developer expected.
|
|
27
|
+
Subsequent steps that reference ${{ env.CONFIG }} or echo the variable and
|
|
28
|
+
pipe it through jq will receive [object Object] and fail to parse — or silently
|
|
29
|
+
produce wrong results.
|
|
30
|
+
|
|
31
|
+
Booleans and numbers survive correctly: fromJSON('true') in env: produces the
|
|
32
|
+
string 'true', and fromJSON('42') produces '42' — these are the only safe uses.
|
|
33
|
+
fix: |
|
|
34
|
+
Pass structured JSON data between steps via GITHUB_OUTPUT (as a raw JSON
|
|
35
|
+
string), then consume the stored string with fromJSON() in downstream
|
|
36
|
+
expression contexts. Never store objects or arrays via fromJSON() in env: blocks.
|
|
37
|
+
fix_code:
|
|
38
|
+
- language: yaml
|
|
39
|
+
label: "Store JSON in GITHUB_OUTPUT, consume via fromJSON in expressions"
|
|
40
|
+
code: |
|
|
41
|
+
jobs:
|
|
42
|
+
build:
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
steps:
|
|
45
|
+
# WRONG: fromJSON object in env: becomes '[object Object]'
|
|
46
|
+
# - name: Bad config
|
|
47
|
+
# env:
|
|
48
|
+
# CONFIG: ${{ fromJSON(inputs.config_json) }}
|
|
49
|
+
# run: echo "$CONFIG" | jq .host # fails: '[object Object]' is not JSON
|
|
50
|
+
|
|
51
|
+
# CORRECT: store raw JSON string in GITHUB_OUTPUT
|
|
52
|
+
- name: Store config
|
|
53
|
+
id: cfg
|
|
54
|
+
run: echo 'config=${{ inputs.config_json }}' >> $GITHUB_OUTPUT
|
|
55
|
+
|
|
56
|
+
# Access fields with fromJSON() directly in expression context
|
|
57
|
+
- name: Use config
|
|
58
|
+
run: |
|
|
59
|
+
echo "Host: ${{ fromJSON(steps.cfg.outputs.config).host }}"
|
|
60
|
+
echo "Port: ${{ fromJSON(steps.cfg.outputs.config).port }}"
|
|
61
|
+
|
|
62
|
+
# Scalar fromJSON values are safe in env: (boolean, number)
|
|
63
|
+
- name: Scalar fromJSON is fine
|
|
64
|
+
env:
|
|
65
|
+
IS_PROD: ${{ fromJSON(inputs.is_prod) }}
|
|
66
|
+
TIMEOUT: ${{ fromJSON(inputs.timeout) }}
|
|
67
|
+
run: echo "prod=$IS_PROD timeout=$TIMEOUT"
|
|
68
|
+
prevention:
|
|
69
|
+
- "Never assign fromJSON() results that produce objects or arrays to env: variables."
|
|
70
|
+
- "Use fromJSON() inline in expression contexts (${{ fromJSON(x).field }}) rather than materializing into env vars."
|
|
71
|
+
- "Store JSON blobs as raw strings in GITHUB_OUTPUT and parse with jq in shell or fromJSON() in expressions."
|
|
72
|
+
- "For object env vars, keep the value as a raw JSON string and parse in shell with jq or python -c."
|
|
73
|
+
docs:
|
|
74
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#fromjson"
|
|
75
|
+
label: "GitHub Actions: fromJSON expression function"
|
|
76
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs"
|
|
77
|
+
label: "GitHub Actions: Passing information between jobs"
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
id: triggers-053
|
|
2
|
+
title: "Unknown or misspelled values in on.<event>.types[] are silently ignored — workflow never triggers"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- types-filter
|
|
7
|
+
- trigger
|
|
8
|
+
- typo
|
|
9
|
+
- pull-request
|
|
10
|
+
- silent-failure
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'types\s*:\s*\[.*(?:syncronize|synchronised|synchronize d|re-opened|labeld|assign)\]'
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: 'types\s*:\s*\n(?:\s+-\s+\w+\n)*\s+-\s+(?!opened|closed|reopened|synchronize|labeled|unlabeled|assigned|unassigned|review_requested|ready_for_review|converted_to_draft|locked|unlocked|milestoned|demilestoned|edited)\w'
|
|
15
|
+
flags: im
|
|
16
|
+
error_messages:
|
|
17
|
+
- "Workflow is not triggered"
|
|
18
|
+
- "No runs found"
|
|
19
|
+
root_cause: |
|
|
20
|
+
GitHub Actions silently discards unrecognized values in the types: filter
|
|
21
|
+
array for pull_request, pull_request_target, and other typed events. There is
|
|
22
|
+
no validation error, no schema warning, and no run log entry — the workflow
|
|
23
|
+
simply never fires for the intended activity type.
|
|
24
|
+
|
|
25
|
+
The most common cause is a typo in a well-known type name:
|
|
26
|
+
- syncronize (correct: synchronize)
|
|
27
|
+
- re-opened (correct: reopened)
|
|
28
|
+
- labeld (correct: labeled)
|
|
29
|
+
- closed (correct: closed — note: not 'merged')
|
|
30
|
+
|
|
31
|
+
A second common cause is listing only the new type while forgetting that the
|
|
32
|
+
default types are no longer active. For example, types: [synchronize] drops
|
|
33
|
+
the implicit opened and reopened behaviors, so a newly opened PR will not
|
|
34
|
+
trigger CI at all.
|
|
35
|
+
|
|
36
|
+
actionlint >=0.7.0 can detect unknown type values but is not always used in
|
|
37
|
+
local development workflows.
|
|
38
|
+
fix: |
|
|
39
|
+
Verify all type values against the official list for each event. Include all
|
|
40
|
+
intended types explicitly — once types: is specified, the defaults no longer
|
|
41
|
+
apply.
|
|
42
|
+
fix_code:
|
|
43
|
+
- language: yaml
|
|
44
|
+
label: "Correct types for pull_request CI — always include all intended types"
|
|
45
|
+
code: |
|
|
46
|
+
on:
|
|
47
|
+
pull_request:
|
|
48
|
+
# WRONG examples (silently never trigger):
|
|
49
|
+
# types: [opened, syncronize, re-opened] # typos
|
|
50
|
+
# types: [synchronize] # drops opened/reopened
|
|
51
|
+
|
|
52
|
+
# CORRECT: include all intended activity types
|
|
53
|
+
types:
|
|
54
|
+
- opened
|
|
55
|
+
- synchronize
|
|
56
|
+
- reopened
|
|
57
|
+
|
|
58
|
+
- language: yaml
|
|
59
|
+
label: "All valid pull_request types for reference"
|
|
60
|
+
code: |
|
|
61
|
+
on:
|
|
62
|
+
pull_request:
|
|
63
|
+
types:
|
|
64
|
+
# Commonly needed for CI:
|
|
65
|
+
- opened # new PR created
|
|
66
|
+
- synchronize # new commit pushed to PR branch
|
|
67
|
+
- reopened # closed PR re-opened
|
|
68
|
+
# Useful for label-gated workflows:
|
|
69
|
+
- labeled
|
|
70
|
+
- unlabeled
|
|
71
|
+
# Draft/review flow:
|
|
72
|
+
- ready_for_review
|
|
73
|
+
- converted_to_draft
|
|
74
|
+
# Assignment:
|
|
75
|
+
- assigned
|
|
76
|
+
- unassigned
|
|
77
|
+
- review_requested
|
|
78
|
+
prevention:
|
|
79
|
+
- "Run actionlint (>=0.7.0) in CI to detect unknown type values — it reports them as errors."
|
|
80
|
+
- "When adding types:, explicitly list ALL types you need — defaults no longer apply once types: is present."
|
|
81
|
+
- "After changing types:, trigger one manual test run per type to confirm the filter is working."
|
|
82
|
+
- "Use the GitHub Actions VS Code extension — it underlines unknown type values as warnings."
|
|
83
|
+
docs:
|
|
84
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request"
|
|
85
|
+
label: "GitHub Actions: pull_request event types"
|
|
86
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_target"
|
|
87
|
+
label: "GitHub Actions: pull_request_target event types"
|
|
88
|
+
- url: "https://rhysd.github.io/actionlint/"
|
|
89
|
+
label: "actionlint: GitHub Actions linter"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
id: yaml-syntax-050
|
|
2
|
+
title: "secrets context used in job/step if: condition rejected — only valid in env: and with: blocks"
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- secrets
|
|
7
|
+
- if-condition
|
|
8
|
+
- expression-context
|
|
9
|
+
- actionlint
|
|
10
|
+
- context-availability
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'Unrecognized named-value: ''secrets'''
|
|
13
|
+
flags: i
|
|
14
|
+
- regex: 'Context access might be invalid: secrets'
|
|
15
|
+
flags: i
|
|
16
|
+
error_messages:
|
|
17
|
+
- "Unrecognized named-value: 'secrets'. Located at position 1 within expression"
|
|
18
|
+
- "Context access might be invalid: secrets"
|
|
19
|
+
root_cause: |
|
|
20
|
+
The secrets context is only available inside env: blocks and action input with: blocks.
|
|
21
|
+
It cannot be used in if: conditions at the job or step level.
|
|
22
|
+
|
|
23
|
+
A common pattern is to conditionally run a deployment step only when a secret is
|
|
24
|
+
configured: if: secrets.DEPLOY_TOKEN != ''. GitHub's expression evaluator rejects
|
|
25
|
+
this at runtime with "Unrecognized named-value: 'secrets'", failing the step or job
|
|
26
|
+
before it even starts. actionlint reports this as a static analysis error under
|
|
27
|
+
"Context access might be invalid: secrets".
|
|
28
|
+
|
|
29
|
+
The restriction is documented in GitHub's context availability table: the secrets
|
|
30
|
+
context is listed as unavailable for job.<job_id>.if and steps.<step_id>.if.
|
|
31
|
+
fix: |
|
|
32
|
+
Map the secret to an environment variable in an env: block, then check the
|
|
33
|
+
environment variable in the if: condition using the env context. For a job-level
|
|
34
|
+
gate, set the env var at the job level and reference it in a step if: condition.
|
|
35
|
+
fix_code:
|
|
36
|
+
- language: yaml
|
|
37
|
+
label: "Bridge secret through env var for step-level if:"
|
|
38
|
+
code: |
|
|
39
|
+
# WRONG: secrets context not available in if: conditions
|
|
40
|
+
# - name: Deploy
|
|
41
|
+
# if: secrets.DEPLOY_TOKEN != ''
|
|
42
|
+
# run: ./deploy.sh
|
|
43
|
+
|
|
44
|
+
# CORRECT: map secret to env var, check env var in if:
|
|
45
|
+
- name: Deploy
|
|
46
|
+
if: env.DEPLOY_TOKEN != ''
|
|
47
|
+
env:
|
|
48
|
+
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
|
|
49
|
+
run: ./deploy.sh
|
|
50
|
+
|
|
51
|
+
- language: yaml
|
|
52
|
+
label: "Job-level env gate evaluated in step if:"
|
|
53
|
+
code: |
|
|
54
|
+
jobs:
|
|
55
|
+
deploy:
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
env:
|
|
58
|
+
# Set at job level so all steps can reference it
|
|
59
|
+
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
|
|
60
|
+
steps:
|
|
61
|
+
- name: Deploy
|
|
62
|
+
if: env.DEPLOY_TOKEN != ''
|
|
63
|
+
run: ./deploy.sh
|
|
64
|
+
prevention:
|
|
65
|
+
- "Never reference secrets.* in if: conditions — always bridge through an env: variable first."
|
|
66
|
+
- "Run actionlint in CI to catch 'Context access might be invalid: secrets' errors before they reach production."
|
|
67
|
+
- "Consider using a boolean repository variable (vars.DEPLOY_ENABLED) as a conditional gate rather than checking secret presence."
|
|
68
|
+
docs:
|
|
69
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#context-availability"
|
|
70
|
+
label: "GitHub Actions: Context availability"
|
|
71
|
+
- url: "https://rhysd.github.io/actionlint/checks.html#check-contexts-and-special-functions"
|
|
72
|
+
label: "actionlint: Context availability checks"
|
package/package.json
CHANGED