@htekdev/actions-debugger 1.0.61 → 1.0.63
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-key-github-sha-no-restore.yml +72 -0
- package/errors/permissions-auth/github-token-fork-pr-read-only.yml +114 -0
- package/errors/permissions-auth/permissions-auth-044.yml +82 -0
- package/errors/runner-environment/runner-environment-123.yml +61 -0
- package/errors/runner-environment/runner-environment-124.yml +68 -0
- package/errors/silent-failures/cache-hit-output-empty-string-not-false.yml +87 -0
- package/errors/silent-failures/silent-failures-062.yml +66 -0
- package/errors/triggers/push-paths-filter-no-effect-workflow-dispatch.yml +81 -0
- package/package.json +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
id: caching-artifacts-042
|
|
2
|
+
title: "Cache key containing github.sha always misses on restore — cache is saved but never reused"
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- cache-key
|
|
8
|
+
- github-sha
|
|
9
|
+
- cache-miss
|
|
10
|
+
- restore-keys
|
|
11
|
+
- performance
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Cache not found for input keys: .*\b[0-9a-f]{40}\b'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'Warning: Cache not found for.*sha.*\.'
|
|
16
|
+
flags: i
|
|
17
|
+
- regex: 'Cache restored from key: .*\b[0-9a-f]{40}\b'
|
|
18
|
+
flags: i
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Cache not found for input keys: Linux-node-a3f2b1c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9"
|
|
21
|
+
- "Warning: Cache not found for key: ubuntu-latest-npm-abc1234def5678901234567890abcdef12345678"
|
|
22
|
+
root_cause: |
|
|
23
|
+
When a cache key includes ${{ github.sha }}, every commit produces a unique key. The cache is
|
|
24
|
+
saved successfully at the end of the workflow, but on the very next run the sha is different,
|
|
25
|
+
so the restore step finds no matching cache entry and declares a miss. This pattern ensures
|
|
26
|
+
the cache is written once and thrown away — it provides no performance benefit.
|
|
27
|
+
|
|
28
|
+
This mistake is common because github.sha is a readily available context value and its use
|
|
29
|
+
in cache keys looks reasonable at first glance. The cache action's restore-keys fallback
|
|
30
|
+
mechanism would normally rescue a miss, but developers often omit restore-keys when using
|
|
31
|
+
sha-based keys, expecting the primary key to match.
|
|
32
|
+
fix: |
|
|
33
|
+
Replace github.sha with a hash of the files that determine cache validity, such as
|
|
34
|
+
hashFiles('**/package-lock.json') for npm or hashFiles('**/go.sum') for Go. Use github.sha
|
|
35
|
+
only as a restore-keys fallback prefix if you want a per-commit cache layer on top of a
|
|
36
|
+
stable base cache. The cache key should be stable across commits unless the relevant
|
|
37
|
+
dependency manifest changes.
|
|
38
|
+
fix_code:
|
|
39
|
+
- language: yaml
|
|
40
|
+
label: "Correct cache key using hashFiles instead of github.sha"
|
|
41
|
+
code: |
|
|
42
|
+
- name: Cache node modules
|
|
43
|
+
uses: actions/cache@v4
|
|
44
|
+
with:
|
|
45
|
+
path: ~/.npm
|
|
46
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
47
|
+
restore-keys: |
|
|
48
|
+
${{ runner.os }}-node-
|
|
49
|
+
- language: yaml
|
|
50
|
+
label: "Per-commit layer on top of a stable base cache (advanced)"
|
|
51
|
+
code: |
|
|
52
|
+
# Stable key hits on re-runs; sha layer saves per-commit installs
|
|
53
|
+
- name: Cache node modules
|
|
54
|
+
uses: actions/cache@v4
|
|
55
|
+
with:
|
|
56
|
+
path: ~/.npm
|
|
57
|
+
key: ${{ runner.os }}-node-${{ github.sha }}
|
|
58
|
+
restore-keys: |
|
|
59
|
+
${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
60
|
+
${{ runner.os }}-node-
|
|
61
|
+
prevention:
|
|
62
|
+
- "Never use github.sha as a primary cache key unless you explicitly want a per-commit cache that never restores"
|
|
63
|
+
- "Use hashFiles() over the relevant lock file or manifest as the stable portion of the cache key"
|
|
64
|
+
- "Always add restore-keys with progressively broader prefixes to benefit from partial cache hits"
|
|
65
|
+
- "Check the cache hit rate in the Actions UI — 0% hit rate on restore is a sign the key is too unique"
|
|
66
|
+
docs:
|
|
67
|
+
- url: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#using-the-cache-action
|
|
68
|
+
label: "GitHub Docs: Using the cache action"
|
|
69
|
+
- url: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#matching-a-cache-key
|
|
70
|
+
label: "GitHub Docs: Matching a cache key"
|
|
71
|
+
- url: https://stackoverflow.com/questions/59435153/github-actions-cache-miss-every-run
|
|
72
|
+
label: "Stack Overflow: GitHub Actions cache miss every run"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
id: permissions-auth-045
|
|
2
|
+
title: "GITHUB_TOKEN has read-only access in pull_request workflows from forks — write operations return 403"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- github-token
|
|
7
|
+
- fork
|
|
8
|
+
- pull-request
|
|
9
|
+
- read-only
|
|
10
|
+
- permissions
|
|
11
|
+
- resource-not-accessible
|
|
12
|
+
- write-access
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'Resource not accessible by integration'
|
|
15
|
+
flags: i
|
|
16
|
+
- regex: 'HttpError: Resource not accessible by integration'
|
|
17
|
+
flags: i
|
|
18
|
+
- regex: 'Error: HttpError: Not Found.*token.*forked'
|
|
19
|
+
flags: i
|
|
20
|
+
- regex: 'refused.*write.*403.*pull_request.*fork'
|
|
21
|
+
flags: i
|
|
22
|
+
error_messages:
|
|
23
|
+
- "Error: Resource not accessible by integration"
|
|
24
|
+
- "RequestError [HttpError]: Resource not accessible by integration"
|
|
25
|
+
- "Error: HttpError: Resource not accessible by integration"
|
|
26
|
+
root_cause: |
|
|
27
|
+
When a pull_request workflow is triggered by a PR from a forked repository, GitHub
|
|
28
|
+
automatically restricts the GITHUB_TOKEN to read-only permissions. This security measure
|
|
29
|
+
prevents malicious PRs from using the token to modify the base repository — the token
|
|
30
|
+
cannot create issues, post comments, create releases, trigger deployments, or write to
|
|
31
|
+
the repository. Workflows see a token that behaves as if all write permissions were denied.
|
|
32
|
+
|
|
33
|
+
This restriction applies even if the workflow YAML has explicit permissions: write entries.
|
|
34
|
+
The fork security boundary takes precedence. The workflow still runs (unlike pull_request_target
|
|
35
|
+
which runs with base repo permissions), but the token scope is silently reduced.
|
|
36
|
+
|
|
37
|
+
Common victims include: posting PR comments with github-script, uploading artifacts to
|
|
38
|
+
releases, creating check runs, and updating commit statuses.
|
|
39
|
+
fix: |
|
|
40
|
+
Use the pull_request_target event if the workflow must write to the base repo. Be aware
|
|
41
|
+
that pull_request_target checks out the base branch by default (not the PR head) and carries
|
|
42
|
+
security implications — never checkout and execute untrusted PR code with write-permission
|
|
43
|
+
tokens (see pwn request attacks). For safe comment posting, split into two workflows:
|
|
44
|
+
a read-only pull_request workflow that uploads results as an artifact, and a
|
|
45
|
+
pull_request_target or workflow_run workflow with write access that downloads the artifact
|
|
46
|
+
and posts the comment.
|
|
47
|
+
fix_code:
|
|
48
|
+
- language: yaml
|
|
49
|
+
label: "Split pattern — upload results in pull_request, post comment in workflow_run"
|
|
50
|
+
code: |
|
|
51
|
+
# Workflow 1: pr-test.yml — runs on pull_request (fork-safe, read-only token)
|
|
52
|
+
on: pull_request
|
|
53
|
+
|
|
54
|
+
jobs:
|
|
55
|
+
test:
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
steps:
|
|
58
|
+
- uses: actions/checkout@v4
|
|
59
|
+
- name: Run tests and save results
|
|
60
|
+
run: npm test > test-results.txt 2>&1 || true
|
|
61
|
+
- name: Upload PR number for downstream workflow
|
|
62
|
+
uses: actions/upload-artifact@v4
|
|
63
|
+
with:
|
|
64
|
+
name: pr-results
|
|
65
|
+
path: |
|
|
66
|
+
test-results.txt
|
|
67
|
+
- language: yaml
|
|
68
|
+
label: "Workflow 2 — post comment using workflow_run (has write token)"
|
|
69
|
+
code: |
|
|
70
|
+
# Workflow 2: pr-comment.yml — runs after pr-test.yml completes (has write token)
|
|
71
|
+
on:
|
|
72
|
+
workflow_run:
|
|
73
|
+
workflows: ["PR Tests"]
|
|
74
|
+
types: [completed]
|
|
75
|
+
|
|
76
|
+
permissions:
|
|
77
|
+
pull-requests: write
|
|
78
|
+
|
|
79
|
+
jobs:
|
|
80
|
+
comment:
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
steps:
|
|
83
|
+
- name: Download test results artifact
|
|
84
|
+
uses: actions/download-artifact@v4
|
|
85
|
+
with:
|
|
86
|
+
name: pr-results
|
|
87
|
+
run-id: ${{ github.event.workflow_run.id }}
|
|
88
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
89
|
+
- name: Post PR comment
|
|
90
|
+
uses: actions/github-script@v7
|
|
91
|
+
with:
|
|
92
|
+
script: |
|
|
93
|
+
const fs = require('fs');
|
|
94
|
+
const results = fs.readFileSync('test-results.txt', 'utf8');
|
|
95
|
+
await github.rest.issues.createComment({
|
|
96
|
+
owner: context.repo.owner,
|
|
97
|
+
repo: context.repo.repo,
|
|
98
|
+
issue_number: context.payload.workflow_run.pull_requests[0].number,
|
|
99
|
+
body: '## Test Results\n```\n' + results + '\n```'
|
|
100
|
+
});
|
|
101
|
+
prevention:
|
|
102
|
+
- "Know that GITHUB_TOKEN is read-only for PR workflows from forks — this is a security feature, not a misconfiguration"
|
|
103
|
+
- "Never use pull_request_target to run PR head code with write-permission tokens — this creates a pwn request vulnerability"
|
|
104
|
+
- "Use the upload-artifact + workflow_run split pattern for safe cross-fork write operations"
|
|
105
|
+
- "Test workflows with a fork PR specifically if they perform write operations — the token scope difference won't appear in same-repo PRs"
|
|
106
|
+
docs:
|
|
107
|
+
- url: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request
|
|
108
|
+
label: "GitHub Docs: pull_request event — fork permission restrictions"
|
|
109
|
+
- url: https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
|
|
110
|
+
label: "GitHub Docs: GITHUB_TOKEN permissions"
|
|
111
|
+
- url: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
|
|
112
|
+
label: "GitHub Security Lab: Preventing pwn requests (pull_request_target risks)"
|
|
113
|
+
- url: https://stackoverflow.com/questions/70435286/resource-not-accessible-by-integration-on-github-pull-request-event
|
|
114
|
+
label: "Stack Overflow: Resource not accessible by integration on pull_request"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
id: permissions-auth-044
|
|
2
|
+
title: "OIDC token sub claim format changes inside reusable workflow jobs, breaking cloud provider trust policies"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- oidc
|
|
7
|
+
- reusable-workflow
|
|
8
|
+
- aws
|
|
9
|
+
- gcp
|
|
10
|
+
- trust-policy
|
|
11
|
+
- sub-claim
|
|
12
|
+
- job-workflow-ref
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'Not authorized to perform sts:AssumeRoleWithWebIdentity'
|
|
15
|
+
flags: i
|
|
16
|
+
- regex: 'failed to generate Google Cloud federated token'
|
|
17
|
+
flags: i
|
|
18
|
+
- regex: 'Credentials could not be loaded.*Could not load credentials from any providers'
|
|
19
|
+
flags: i
|
|
20
|
+
error_messages:
|
|
21
|
+
- "An error occurred (AccessDenied) when calling the AssumeRoleWithWebIdentity operation: Not authorized to perform sts:AssumeRoleWithWebIdentity"
|
|
22
|
+
- "Error: google-github-actions/auth failed to generate Google Cloud federated token for..."
|
|
23
|
+
- "Error: Credentials could not be loaded, please check your action inputs: Could not load credentials from any providers"
|
|
24
|
+
root_cause: |
|
|
25
|
+
When a job runs inside a reusable workflow (called via uses:), GitHub changes the format of
|
|
26
|
+
the OIDC token sub claim to include the calling workflow's path. For a direct job the sub is:
|
|
27
|
+
repo:ORG/REPO:ref:refs/heads/main
|
|
28
|
+
For a job inside a reusable workflow the sub becomes:
|
|
29
|
+
repo:ORG/REPO:job_workflow_ref:ORG/REPO/.github/workflows/reusable.yml@refs/heads/main
|
|
30
|
+
AWS IAM OIDC trust policies and GCP Workload Identity Federation attribute conditions that
|
|
31
|
+
were configured to match the simpler ref-based sub format now reject the OIDC token with an
|
|
32
|
+
AccessDenied error. The error message gives no indication that the sub claim format changed —
|
|
33
|
+
it looks identical to any other OIDC trust policy mismatch.
|
|
34
|
+
fix: |
|
|
35
|
+
Update the cloud provider's OIDC trust policy to match the new sub claim format used by
|
|
36
|
+
reusable workflow jobs. For AWS, update the IAM trust policy StringLike condition to match
|
|
37
|
+
job_workflow_ref instead of ref. For GCP, update the attribute condition in the Workload
|
|
38
|
+
Identity Pool provider. Alternatively, use GitHub's OIDC subject claim customization feature
|
|
39
|
+
(repo Settings → Actions → General → OIDC subject claims) to define a consistent sub claim
|
|
40
|
+
template that works for both caller and reusable workflow jobs.
|
|
41
|
+
fix_code:
|
|
42
|
+
- language: yaml
|
|
43
|
+
label: "Reusable workflow with id-token permission declared (required)"
|
|
44
|
+
code: |
|
|
45
|
+
# In the reusable workflow file (.github/workflows/reusable.yml):
|
|
46
|
+
on:
|
|
47
|
+
workflow_call:
|
|
48
|
+
|
|
49
|
+
permissions:
|
|
50
|
+
id-token: write
|
|
51
|
+
contents: read
|
|
52
|
+
|
|
53
|
+
jobs:
|
|
54
|
+
deploy:
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
steps:
|
|
57
|
+
- name: Configure AWS credentials
|
|
58
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
59
|
+
with:
|
|
60
|
+
role-to-assume: arn:aws:iam::123456789:role/my-role
|
|
61
|
+
aws-region: us-east-1
|
|
62
|
+
- language: yaml
|
|
63
|
+
label: "AWS IAM trust policy StringLike condition for reusable workflow sub claim"
|
|
64
|
+
code: |
|
|
65
|
+
# Update AWS IAM role trust policy Condition block to match reusable workflow sub format.
|
|
66
|
+
# Old (direct job): "repo:ORG/REPO:ref:refs/heads/main"
|
|
67
|
+
# New (reusable job): "repo:ORG/REPO:job_workflow_ref:ORG/REPO/.github/workflows/reusable.yml@refs/heads/main"
|
|
68
|
+
#
|
|
69
|
+
# Use a wildcard to allow both patterns:
|
|
70
|
+
# "token.actions.githubusercontent.com:sub": "StringLike": ["repo:ORG/REPO:*"]
|
|
71
|
+
prevention:
|
|
72
|
+
- "When refactoring direct jobs into reusable workflows, update cloud OIDC trust policies before deploying"
|
|
73
|
+
- "Use GitHub subject claim customization to define a consistent sub format that works across direct and reusable jobs"
|
|
74
|
+
- "Document the expected OIDC sub claim format in the reusable workflow README alongside cloud policy requirements"
|
|
75
|
+
- "Test OIDC authentication in a staging cloud environment when moving jobs into reusable workflows"
|
|
76
|
+
docs:
|
|
77
|
+
- url: https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/using-openid-connect-with-reusable-workflows
|
|
78
|
+
label: "GitHub Docs: Using OIDC with reusable workflows"
|
|
79
|
+
- url: https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-subject-claims-for-an-organization-or-repository
|
|
80
|
+
label: "GitHub Docs: Customizing OIDC subject claims"
|
|
81
|
+
- url: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_oidc.html
|
|
82
|
+
label: "AWS Docs: Creating IAM OIDC identity providers"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
id: runner-environment-123
|
|
2
|
+
title: "actions/checkout@v6 breaks Docker container actions that use git authentication"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- checkout
|
|
7
|
+
- v6
|
|
8
|
+
- docker
|
|
9
|
+
- container-action
|
|
10
|
+
- git-auth
|
|
11
|
+
- breaking-change
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'fatal: could not read Username for.*No such device or address'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'fatal: Authentication failed for.*github\.com'
|
|
16
|
+
flags: i
|
|
17
|
+
- regex: 'fatal: credential helper.*is not executable'
|
|
18
|
+
flags: i
|
|
19
|
+
error_messages:
|
|
20
|
+
- "fatal: could not read Username for 'https://github.com/': No such device or address"
|
|
21
|
+
- "fatal: Authentication failed for 'https://github.com/org/repo.git/'"
|
|
22
|
+
- "Error: The process '/usr/bin/git' failed with exit code 128"
|
|
23
|
+
root_cause: |
|
|
24
|
+
actions/checkout@v6 changed credential storage: credentials are now written to the runner's
|
|
25
|
+
native credential manager rather than the global gitconfig file. Docker container actions run
|
|
26
|
+
in isolated containers without access to the runner host's credential store, so any git
|
|
27
|
+
operations inside a Docker-based action or container: job that require authentication fail.
|
|
28
|
+
This is a breaking change from v5, where credentials were written to gitconfig and could be
|
|
29
|
+
inherited by Docker containers. The v6 runner PR #4011 introduced this mechanism and Docker
|
|
30
|
+
container action support is gated behind a feature flag not yet enabled for all runners.
|
|
31
|
+
fix: |
|
|
32
|
+
Pin to actions/checkout@v5 for workflows that rely on Docker container actions making
|
|
33
|
+
authenticated git calls. Monitor the actions/checkout issue tracker for v6 Docker container
|
|
34
|
+
action support. Alternatively, pass the GITHUB_TOKEN as an environment variable to the Docker
|
|
35
|
+
action and configure credentials inside the container's own entrypoint script.
|
|
36
|
+
fix_code:
|
|
37
|
+
- language: yaml
|
|
38
|
+
label: "Pin to actions/checkout@v5 for Docker container action compatibility"
|
|
39
|
+
code: |
|
|
40
|
+
- name: Checkout repository
|
|
41
|
+
uses: actions/checkout@v5
|
|
42
|
+
with:
|
|
43
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
44
|
+
- language: yaml
|
|
45
|
+
label: "Pass token as env var to Docker action for container-side credential setup"
|
|
46
|
+
code: |
|
|
47
|
+
- name: Run Docker-based action with explicit token
|
|
48
|
+
uses: org/my-docker-action@v1
|
|
49
|
+
env:
|
|
50
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
51
|
+
prevention:
|
|
52
|
+
- "Pin actions/checkout to a tested major version and review release notes before upgrading to a new major"
|
|
53
|
+
- "Audit workflows for Docker container actions that perform authenticated repository operations before upgrading checkout"
|
|
54
|
+
- "Test Docker-based custom actions in CI against the new checkout version before rolling out"
|
|
55
|
+
docs:
|
|
56
|
+
- url: https://github.com/actions/checkout/issues/2313
|
|
57
|
+
label: "actions/checkout#2313: v6 breaks Docker actions that use git authentication"
|
|
58
|
+
- url: https://github.com/orgs/community/discussions/179107
|
|
59
|
+
label: "GitHub Community: actions/checkout v6 changes discussion"
|
|
60
|
+
- url: https://github.com/actions/runner/pull/4011
|
|
61
|
+
label: "actions/runner PR#4011: credential manager changes introduced in v6"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
id: runner-environment-124
|
|
2
|
+
title: "actions/setup-go@v6 GOTOOLCHAIN auto mode downloads unexpected Go version from go.mod toolchain directive"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- setup-go
|
|
7
|
+
- v6
|
|
8
|
+
- gotoolchain
|
|
9
|
+
- go-version
|
|
10
|
+
- toolchain-directive
|
|
11
|
+
- breaking-change
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'go: downloading go\d+\.\d+\.\d+ \('
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'toolchain go\d+\.\d+\.\d+ cannot be used because it would require a later version'
|
|
16
|
+
flags: i
|
|
17
|
+
- regex: 'go: toolchain go\d+\.\d+\.\d+ not available on GOPROXY'
|
|
18
|
+
flags: i
|
|
19
|
+
error_messages:
|
|
20
|
+
- "go: downloading go1.23.4 (linux/amd64)"
|
|
21
|
+
- "toolchain go1.23.4 cannot be used because it would require a later version"
|
|
22
|
+
- "go: toolchain go1.23.4 not available on GOPROXY"
|
|
23
|
+
root_cause: |
|
|
24
|
+
actions/setup-go@v6 (released September 2025) changed toolchain handling to honor Go 1.21+
|
|
25
|
+
GOTOOLCHAIN semantics. When a go.mod file contains a 'toolchain goX.Y.Z' directive and
|
|
26
|
+
GOTOOLCHAIN is set to 'auto' (the default for Go 1.21+), Go will automatically download the
|
|
27
|
+
toolchain version specified in go.mod rather than using the version installed by setup-go.
|
|
28
|
+
The workflow runs with a different Go version than the one specified in the go-version: input,
|
|
29
|
+
causing unexpected behavior, build failures, or unintended Go version usage. In v5, setup-go
|
|
30
|
+
implicitly set GOTOOLCHAIN=local, preventing automatic toolchain downloads. v6 removed this
|
|
31
|
+
implicit override, meaning go.mod toolchain directives now take effect in CI.
|
|
32
|
+
fix: |
|
|
33
|
+
Set GOTOOLCHAIN=local in the step environment to force Go to use exactly the version installed
|
|
34
|
+
by setup-go, ignoring the toolchain directive in go.mod. Alternatively, align the go-version:
|
|
35
|
+
input with the toolchain directive in go.mod, or use go-version-file: go.mod to let setup-go
|
|
36
|
+
read the version directly from the module file.
|
|
37
|
+
fix_code:
|
|
38
|
+
- language: yaml
|
|
39
|
+
label: "Set GOTOOLCHAIN=local to prevent auto-download — use exactly the installed version"
|
|
40
|
+
code: |
|
|
41
|
+
- name: Set up Go
|
|
42
|
+
uses: actions/setup-go@v6
|
|
43
|
+
with:
|
|
44
|
+
go-version: '1.22'
|
|
45
|
+
|
|
46
|
+
- name: Build
|
|
47
|
+
run: go build ./...
|
|
48
|
+
env:
|
|
49
|
+
GOTOOLCHAIN: local # disables auto-download; uses only the version from setup-go
|
|
50
|
+
- language: yaml
|
|
51
|
+
label: "Or read go-version directly from go.mod to stay aligned with the toolchain directive"
|
|
52
|
+
code: |
|
|
53
|
+
- name: Set up Go
|
|
54
|
+
uses: actions/setup-go@v6
|
|
55
|
+
with:
|
|
56
|
+
go-version-file: go.mod # reads 'go X.Y' line from go.mod; stays in sync automatically
|
|
57
|
+
prevention:
|
|
58
|
+
- "After upgrading to setup-go@v6, verify the actual Go version used in builds matches the go-version: input"
|
|
59
|
+
- "Add GOTOOLCHAIN=local to workflow env or per-step env to opt out of automatic toolchain download behavior"
|
|
60
|
+
- "Keep the go.mod toolchain directive in sync with the go-version: value specified in setup-go"
|
|
61
|
+
- "Use go-version-file: go.mod instead of an explicit go-version: value to stay automatically aligned"
|
|
62
|
+
docs:
|
|
63
|
+
- url: https://github.com/actions/setup-go/releases/tag/v6.0.0
|
|
64
|
+
label: "actions/setup-go v6.0.0 release notes — toolchain handling breaking change"
|
|
65
|
+
- url: https://github.com/actions/setup-go/pull/460
|
|
66
|
+
label: "actions/setup-go PR#460: Improve toolchain handling"
|
|
67
|
+
- url: https://go.dev/doc/toolchain
|
|
68
|
+
label: "Go documentation: Toolchains — GOTOOLCHAIN environment variable"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
id: silent-failures-063
|
|
2
|
+
title: "actions/cache cache-hit output is empty string on miss, not 'false' — equality checks silently never match"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- actions-cache
|
|
7
|
+
- cache-hit
|
|
8
|
+
- output
|
|
9
|
+
- empty-string
|
|
10
|
+
- if-condition
|
|
11
|
+
- false-comparison
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'steps\.\w+\.outputs\.cache-hit\s*==\s*[''"]false[''"]'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: "cache-hit.*==.*'false'"
|
|
16
|
+
flags: i
|
|
17
|
+
error_messages:
|
|
18
|
+
- "cache-hit output comparison to 'false' always evaluates to false — use != 'true' instead"
|
|
19
|
+
root_cause: |
|
|
20
|
+
The actions/cache action sets the cache-hit output to the string 'true' when a cache is
|
|
21
|
+
restored from an exact key match, and to an empty string '' when the cache is not found
|
|
22
|
+
(including when restored from a restore-keys fallback, which returns '' for cache-hit in v3
|
|
23
|
+
and 'false' only in some v4 configurations). This means that
|
|
24
|
+
`if: steps.cache.outputs.cache-hit == 'false'` evaluates to false on a cache miss because
|
|
25
|
+
'' != 'false', so the step or job it guards is silently skipped.
|
|
26
|
+
|
|
27
|
+
This is one of the most upvoted GitHub Actions questions on Stack Overflow. Developers
|
|
28
|
+
naturally expect a boolean-like output to be 'true' or 'false', but the cache action uses
|
|
29
|
+
'true' vs '' (empty) semantics. The step appears to run successfully; nothing in the logs
|
|
30
|
+
indicates the if condition did not match as expected.
|
|
31
|
+
fix: |
|
|
32
|
+
Check for cache miss using != 'true' instead of == 'false'. An empty string and 'false'
|
|
33
|
+
both evaluate to not-equal-to-'true', so this pattern correctly handles all cache miss
|
|
34
|
+
cases across cache action versions. Alternatively, use the boolean expression
|
|
35
|
+
`steps.cache.outputs.cache-hit != 'true'` or omit the check and rely on the cache action's
|
|
36
|
+
lookup-only: true parameter combined with a separate save step.
|
|
37
|
+
fix_code:
|
|
38
|
+
- language: yaml
|
|
39
|
+
label: "Use != 'true' to reliably detect cache miss (works across all cache action versions)"
|
|
40
|
+
code: |
|
|
41
|
+
- name: Cache dependencies
|
|
42
|
+
id: cache-deps
|
|
43
|
+
uses: actions/cache@v4
|
|
44
|
+
with:
|
|
45
|
+
path: ~/.npm
|
|
46
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
47
|
+
|
|
48
|
+
# WRONG — silently never runs on cache miss:
|
|
49
|
+
# - name: Install deps
|
|
50
|
+
# if: steps.cache-deps.outputs.cache-hit == 'false'
|
|
51
|
+
|
|
52
|
+
# CORRECT — matches both '' and 'false':
|
|
53
|
+
- name: Install dependencies
|
|
54
|
+
if: steps.cache-deps.outputs.cache-hit != 'true'
|
|
55
|
+
run: npm ci
|
|
56
|
+
- language: yaml
|
|
57
|
+
label: "Lookup-only + explicit save pattern (cache@v4) to avoid output ambiguity"
|
|
58
|
+
code: |
|
|
59
|
+
- name: Restore cache (lookup only, no auto-save)
|
|
60
|
+
id: cache-restore
|
|
61
|
+
uses: actions/cache/restore@v4
|
|
62
|
+
with:
|
|
63
|
+
path: ~/.npm
|
|
64
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
65
|
+
|
|
66
|
+
- name: Install dependencies
|
|
67
|
+
if: steps.cache-restore.outputs.cache-hit != 'true'
|
|
68
|
+
run: npm ci
|
|
69
|
+
|
|
70
|
+
- name: Save cache
|
|
71
|
+
if: steps.cache-restore.outputs.cache-hit != 'true'
|
|
72
|
+
uses: actions/cache/save@v4
|
|
73
|
+
with:
|
|
74
|
+
path: ~/.npm
|
|
75
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
76
|
+
prevention:
|
|
77
|
+
- "Always use != 'true' to check for cache miss — never use == 'false' on cache action outputs"
|
|
78
|
+
- "Review the cache action version changelog: v3 uses '' on miss, v4 behavior depends on exact/partial hit"
|
|
79
|
+
- "Add a debug step that echoes cache-hit output to make cache behavior visible in logs during development"
|
|
80
|
+
- "Consult the cache action README for the exact semantics of cache-hit, cache-primary-key, and cache-matched-key"
|
|
81
|
+
docs:
|
|
82
|
+
- url: https://github.com/actions/cache#outputs
|
|
83
|
+
label: "actions/cache README: Outputs"
|
|
84
|
+
- url: https://stackoverflow.com/questions/62754195/github-actions-cache-hit-is-true-but-id-expect-false
|
|
85
|
+
label: "Stack Overflow: cache-hit is empty string, not 'false'"
|
|
86
|
+
- url: https://github.com/actions/cache/issues/1699
|
|
87
|
+
label: "actions/cache#1699: cache-hit output '' vs 'false' confusion"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
id: silent-failures-062
|
|
2
|
+
title: "actions/cache save failure emits Warning annotation but does not fail the workflow step"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- cache-save
|
|
8
|
+
- warning
|
|
9
|
+
- non-fatal
|
|
10
|
+
- false-success
|
|
11
|
+
- cache-service
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Warning: Failed to save:.*Failed to CreateCacheEntry.*non-retryable'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'Warning: Failed to restore:.*Failed to GetCacheEntryDownloadURL.*non-retryable'
|
|
16
|
+
flags: i
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Warning: Failed to save: Failed to CreateCacheEntry: Received non-retryable error: Failed request: (404) Not Found: invalid request"
|
|
19
|
+
- "Warning: Failed to restore: Failed to GetCacheEntryDownloadURL: Received non-retryable error: Failed request: (404) Not Found: invalid request"
|
|
20
|
+
root_cause: |
|
|
21
|
+
The actions/cache and actions/cache/save actions treat upload failures as non-fatal warnings
|
|
22
|
+
rather than step errors. When the GitHub cache backend returns an error (4xx/5xx), is
|
|
23
|
+
unavailable, or rejects the request, the action emits a yellow Warning annotation in the
|
|
24
|
+
workflow log but exits with code 0. The workflow step is marked green (success). Downstream
|
|
25
|
+
runs then see genuine "Cache not found" misses since nothing was persisted. Developers assume
|
|
26
|
+
the cache was saved based on the green checkmark, spending hours debugging unreliable cache
|
|
27
|
+
restore before finding the Warning annotation buried in the save step output. This behavior is
|
|
28
|
+
intentional — cache is treated as an optimization, not a requirement — but it means failures
|
|
29
|
+
are invisible unless annotations are actively checked.
|
|
30
|
+
fix: |
|
|
31
|
+
Check step-level annotations (the yellow warning triangle icon) on cache save steps, not just
|
|
32
|
+
the green/red status indicator. Use fail-on-cache-miss: true on restore steps when cache
|
|
33
|
+
availability is critical to your build speed so that a missing cache surfaces as a hard
|
|
34
|
+
failure. For save failures there is no built-in fail flag — add a downstream validation step
|
|
35
|
+
if guaranteed cache persistence is required. Always use supported cache action versions
|
|
36
|
+
(actions/cache@v3 or @v4) to ensure compatibility with the current cache backend service.
|
|
37
|
+
fix_code:
|
|
38
|
+
- language: yaml
|
|
39
|
+
label: "Use fail-on-cache-miss on restore to surface missing cache as an error"
|
|
40
|
+
code: |
|
|
41
|
+
- name: Restore build cache
|
|
42
|
+
id: restore-cache
|
|
43
|
+
uses: actions/cache/restore@v4
|
|
44
|
+
with:
|
|
45
|
+
key: ${{ runner.os }}-build-${{ hashFiles('**/package-lock.json') }}
|
|
46
|
+
path: ~/.npm
|
|
47
|
+
fail-on-cache-miss: true # step fails with error if nothing was previously saved
|
|
48
|
+
|
|
49
|
+
- name: Save build cache
|
|
50
|
+
if: always()
|
|
51
|
+
uses: actions/cache/save@v4
|
|
52
|
+
with:
|
|
53
|
+
key: ${{ runner.os }}-build-${{ hashFiles('**/package-lock.json') }}
|
|
54
|
+
path: ~/.npm
|
|
55
|
+
prevention:
|
|
56
|
+
- "Always check workflow annotations (yellow warning triangle) in addition to the step pass/fail status"
|
|
57
|
+
- "Use actions/cache@v4 or @v3 — deprecated pinned SHAs may fail silently after the cache backend migration"
|
|
58
|
+
- "Use fail-on-cache-miss: true on restore steps to make cache misses visible as hard failures"
|
|
59
|
+
- "Monitor the actions/cache issue tracker when cache restore reliability degrades — backend incidents are reported quickly"
|
|
60
|
+
docs:
|
|
61
|
+
- url: https://github.com/actions/cache/issues/1541
|
|
62
|
+
label: "actions/cache#1541: Bug: Failed to CreateCacheEntry (29 reactions, Feb 2025)"
|
|
63
|
+
- url: https://github.com/actions/cache/discussions/1510
|
|
64
|
+
label: "actions/cache Discussion#1510: Deprecation Notice — upgrade to latest before Feb 2025"
|
|
65
|
+
- url: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows
|
|
66
|
+
label: "GitHub Docs: Caching dependencies — fail-on-cache-miss option"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
id: triggers-045
|
|
2
|
+
title: "on.push.paths filter has no effect on workflow_dispatch — manual trigger always runs regardless of changed files"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- workflow-dispatch
|
|
7
|
+
- push
|
|
8
|
+
- paths-filter
|
|
9
|
+
- triggers
|
|
10
|
+
- manual-trigger
|
|
11
|
+
- always-runs
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'workflow_dispatch'
|
|
14
|
+
flags: i
|
|
15
|
+
error_messages:
|
|
16
|
+
- "Workflow ran unexpectedly when triggered via workflow_dispatch despite paths filter"
|
|
17
|
+
root_cause: |
|
|
18
|
+
GitHub Actions evaluates on.push.paths (and on.push.paths-ignore) filters only for push
|
|
19
|
+
events. The filter is tied to the event type it is defined under. When workflow_dispatch is
|
|
20
|
+
added as a separate trigger, it has no paths context — there is no commit diff associated
|
|
21
|
+
with a manual dispatch. GitHub therefore runs the workflow unconditionally when dispatched.
|
|
22
|
+
|
|
23
|
+
This surprises developers who add workflow_dispatch: to an existing push-triggered workflow
|
|
24
|
+
for convenience and expect the paths filter to still gate the run. The workflow_dispatch
|
|
25
|
+
event has no paths or branches key at all; any such keys would be silently ignored if added.
|
|
26
|
+
|
|
27
|
+
A related confusion: paths filters on pull_request or push do not protect against
|
|
28
|
+
workflow_dispatch, so adding a workflow_dispatch trigger effectively creates an unfiltered
|
|
29
|
+
entry point into the workflow.
|
|
30
|
+
fix: |
|
|
31
|
+
Accept that workflow_dispatch runs unconditionally — this is intentional GitHub behavior.
|
|
32
|
+
If you want to restrict what the dispatched workflow does based on inputs, use
|
|
33
|
+
workflow_dispatch inputs to pass a flag and gate steps with if: conditions. If the goal is
|
|
34
|
+
to limit accidental runs, remove workflow_dispatch from workflows where paths-gating is
|
|
35
|
+
critical, or add a required input that must be confirmed before proceeding.
|
|
36
|
+
fix_code:
|
|
37
|
+
- language: yaml
|
|
38
|
+
label: "Separate push-only workflow (with paths filter) from a dispatch-friendly workflow"
|
|
39
|
+
code: |
|
|
40
|
+
# Option 1: keep push and dispatch as separate workflow files
|
|
41
|
+
# push-filtered.yml — only triggered on relevant path changes
|
|
42
|
+
on:
|
|
43
|
+
push:
|
|
44
|
+
paths:
|
|
45
|
+
- 'src/**'
|
|
46
|
+
|
|
47
|
+
# dispatch-build.yml — no paths filter needed; always intentional
|
|
48
|
+
on:
|
|
49
|
+
workflow_dispatch:
|
|
50
|
+
- language: yaml
|
|
51
|
+
label: "Use a workflow_dispatch input to require confirmation before running"
|
|
52
|
+
code: |
|
|
53
|
+
on:
|
|
54
|
+
push:
|
|
55
|
+
paths:
|
|
56
|
+
- 'src/**'
|
|
57
|
+
workflow_dispatch:
|
|
58
|
+
inputs:
|
|
59
|
+
confirm:
|
|
60
|
+
description: 'Type YES to run regardless of changed files'
|
|
61
|
+
required: true
|
|
62
|
+
|
|
63
|
+
jobs:
|
|
64
|
+
build:
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
steps:
|
|
67
|
+
- name: Proceed only if push triggered by path change OR dispatch confirmed
|
|
68
|
+
if: github.event_name == 'push' || inputs.confirm == 'YES'
|
|
69
|
+
run: echo "Running build"
|
|
70
|
+
prevention:
|
|
71
|
+
- "Understand that paths/branches filters are per-event-type — workflow_dispatch has no file diff context"
|
|
72
|
+
- "Document in the workflow file that workflow_dispatch runs unconditionally to avoid future confusion"
|
|
73
|
+
- "Use workflow_dispatch inputs to gate critical steps when a manual trigger is added to a path-filtered workflow"
|
|
74
|
+
- "Review which workflows have both push.paths filters and workflow_dispatch before adding the dispatch trigger"
|
|
75
|
+
docs:
|
|
76
|
+
- url: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_dispatch
|
|
77
|
+
label: "GitHub Docs: workflow_dispatch event"
|
|
78
|
+
- url: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/triggering-a-workflow#using-filters-to-target-specific-branches-or-tags-for-push-events
|
|
79
|
+
label: "GitHub Docs: Using filters to target specific paths"
|
|
80
|
+
- url: https://stackoverflow.com/questions/65900201/workflow-dispatch-ignores-paths-filter
|
|
81
|
+
label: "Stack Overflow: workflow_dispatch ignores paths filter"
|
package/package.json
CHANGED