@htekdev/actions-debugger 1.0.12 → 1.0.14
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-path-exclusion-glob-silent-noop.yml +83 -0
- package/errors/known-unsolved/composite-action-env-not-updated-on-reuse.yml +114 -0
- package/errors/runner-environment/az-powershell-14-to-15-breaking.yml +108 -0
- package/errors/runner-environment/ubuntu-2204-precached-docker-removed.yml +110 -0
- package/errors/runner-environment/windows-msvc-ltcg-mixed-image-versions.yml +112 -0
- package/errors/silent-failures/app-store-ios26-sdk-required.yml +113 -0
- package/errors/silent-failures/continue-on-error-failure-fn-bypassed.yml +90 -0
- package/errors/silent-failures/if-mixed-expression-always-true.yml +71 -0
- package/errors/yaml-syntax/needs-skipped-if-requires-always.yml +100 -0
- package/package.json +1 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
id: caching-artifacts-014
|
|
2
|
+
title: "actions/cache Exclusion Globs (! patterns) Silently Ignored"
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- glob
|
|
8
|
+
- exclusion
|
|
9
|
+
- negation
|
|
10
|
+
- silent-failure
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "path:(?:[^#\\n]*\\n)+\\s*!\\S"
|
|
13
|
+
flags: "im"
|
|
14
|
+
- regex: "path:.*\\n.*!~/"
|
|
15
|
+
flags: "im"
|
|
16
|
+
error_messages:
|
|
17
|
+
- "Cache saved with key"
|
|
18
|
+
- "Cache hit occurred on the primary key"
|
|
19
|
+
root_cause: |
|
|
20
|
+
The `actions/cache` action uses `@actions/glob` with `implicitDescendants: false`
|
|
21
|
+
to resolve the `path:` input into a file list for `tar`. With this setting, each
|
|
22
|
+
pattern is matched only at the top level — subdirectory enumeration does not happen.
|
|
23
|
+
As a result, negation patterns like `!~/cache/temp` never find anything to exclude
|
|
24
|
+
because there are no implicitly enumerated descendants to match against.
|
|
25
|
+
|
|
26
|
+
The cache save and restore operations complete successfully with no error or warning.
|
|
27
|
+
The "excluded" directory is silently included in the archive, leading to:
|
|
28
|
+
- Larger caches than intended, consuming quota faster
|
|
29
|
+
- Sensitive or temporary files accidentally persisted across runs
|
|
30
|
+
- False-positive cache key hits (excluded dir content changes don't invalidate cache)
|
|
31
|
+
|
|
32
|
+
This is a long-standing known limitation of `actions/cache` that has been reported
|
|
33
|
+
since v1 and remains unfixed in v4.
|
|
34
|
+
fix: |
|
|
35
|
+
`actions/cache` does not support exclusion globs. Restructure directories so the
|
|
36
|
+
content you want to cache lives in a dedicated subdirectory, and reference only that
|
|
37
|
+
path. If directory restructuring is not feasible, use a `run` step to create a
|
|
38
|
+
filtered `tar` archive before caching.
|
|
39
|
+
fix_code:
|
|
40
|
+
- language: yaml
|
|
41
|
+
label: "BROKEN — exclusion pattern silently does nothing"
|
|
42
|
+
code: |
|
|
43
|
+
- uses: actions/cache@v4
|
|
44
|
+
with:
|
|
45
|
+
path: |
|
|
46
|
+
~/cache
|
|
47
|
+
!~/cache/temp # Silently ignored — temp/ is still cached
|
|
48
|
+
key: ${{ runner.os }}-cache-${{ hashFiles('**/package-lock.json') }}
|
|
49
|
+
- language: yaml
|
|
50
|
+
label: "CORRECT — restructure: cache only the subdirectory you need"
|
|
51
|
+
code: |
|
|
52
|
+
# Put persistent cache content in ~/cache/persistent/
|
|
53
|
+
# Put temp files in ~/cache/temp/ (not cached)
|
|
54
|
+
- uses: actions/cache@v4
|
|
55
|
+
with:
|
|
56
|
+
path: ~/cache/persistent # Cache only the isolated subdirectory
|
|
57
|
+
key: ${{ runner.os }}-cache-${{ hashFiles('**/package-lock.json') }}
|
|
58
|
+
- language: yaml
|
|
59
|
+
label: "CORRECT — manual tar with --exclude for complex cases"
|
|
60
|
+
code: |
|
|
61
|
+
- name: Save cache with exclusion
|
|
62
|
+
run: |
|
|
63
|
+
tar --exclude='$HOME/cache/temp' \
|
|
64
|
+
--exclude='$HOME/cache/.git' \
|
|
65
|
+
-czf /tmp/my-cache.tar.gz \
|
|
66
|
+
~/cache
|
|
67
|
+
|
|
68
|
+
- uses: actions/cache@v4
|
|
69
|
+
with:
|
|
70
|
+
path: /tmp/my-cache.tar.gz
|
|
71
|
+
key: ${{ runner.os }}-filtered-${{ hashFiles('**/package-lock.json') }}
|
|
72
|
+
prevention:
|
|
73
|
+
- "Never rely on `!` negation patterns in `actions/cache` `path:` — they are silently ignored."
|
|
74
|
+
- "Audit cache paths to confirm no sensitive, secret, or large temporary files are included."
|
|
75
|
+
- "Isolate cacheable build artifacts into dedicated subdirectories at project setup time."
|
|
76
|
+
- "Set `enableCrossOsArchive: false` (default) and test on each OS independently."
|
|
77
|
+
docs:
|
|
78
|
+
- url: "https://github.com/actions/toolkit/issues/713"
|
|
79
|
+
label: "actions/toolkit#713 — Cache: excluding files or folders with ! not working"
|
|
80
|
+
- url: "https://github.com/actions/cache/blob/main/tips-and-workarounds.md"
|
|
81
|
+
label: "actions/cache — Tips and workarounds"
|
|
82
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows"
|
|
83
|
+
label: "Caching dependencies to speed up workflows"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
id: known-unsolved-013
|
|
2
|
+
title: "GITHUB_ENV Set in Composite Action Not Updated on Repeated Calls in Same Job"
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- composite-action
|
|
7
|
+
- GITHUB_ENV
|
|
8
|
+
- env-var
|
|
9
|
+
- repeated-calls
|
|
10
|
+
- known-limitation
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "echo.*>>\\s*\\$GITHUB_ENV"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "GITHUB_ENV.*composite"
|
|
15
|
+
flags: "i"
|
|
16
|
+
error_messages:
|
|
17
|
+
- "environment variable not updated on second call"
|
|
18
|
+
- "variable retains value from first call"
|
|
19
|
+
root_cause: |
|
|
20
|
+
When a composite action sets an environment variable by appending to $GITHUB_ENV,
|
|
21
|
+
that variable is propagated to the parent job context after the composite action
|
|
22
|
+
completes. On the first call this works correctly. However, if the same composite
|
|
23
|
+
action is called a second time in the same job, the GITHUB_ENV write succeeds
|
|
24
|
+
internally but the parent job environment variable retains the value from the
|
|
25
|
+
first call.
|
|
26
|
+
|
|
27
|
+
This is a known platform limitation: GITHUB_ENV writes from within composite
|
|
28
|
+
actions are processed at step-boundary transitions. On repeated calls to the
|
|
29
|
+
same composite action in the same job, the runner does not re-apply the env
|
|
30
|
+
file changes correctly for variables already set — the first written value
|
|
31
|
+
persists for the job duration.
|
|
32
|
+
|
|
33
|
+
The limitation affects parameterized composite actions used as reusable units
|
|
34
|
+
that produce environment state, especially when called in loops, matrices, or
|
|
35
|
+
sequential steps with different inputs.
|
|
36
|
+
|
|
37
|
+
GitHub has not fixed this behavior; the recommended migration path is to use
|
|
38
|
+
GITHUB_OUTPUT instead of GITHUB_ENV for composite action outputs.
|
|
39
|
+
fix: |
|
|
40
|
+
Replace $GITHUB_ENV writes with $GITHUB_OUTPUT writes inside composite actions.
|
|
41
|
+
Step outputs are properly scoped per-invocation and do not have the repeated-call
|
|
42
|
+
limitation. Read the output in the calling workflow using steps.<id>.outputs.<name>.
|
|
43
|
+
|
|
44
|
+
If environment variables are strictly required at the job level, set them in the
|
|
45
|
+
calling workflow from the composite action outputs, not inside the composite action.
|
|
46
|
+
fix_code:
|
|
47
|
+
- language: yaml
|
|
48
|
+
label: "BROKEN composite action — GITHUB_ENV silently not updated on 2nd call"
|
|
49
|
+
code: |
|
|
50
|
+
# my-action/action.yml
|
|
51
|
+
inputs:
|
|
52
|
+
label:
|
|
53
|
+
required: true
|
|
54
|
+
runs:
|
|
55
|
+
using: composite
|
|
56
|
+
steps:
|
|
57
|
+
- run: echo "MY_LABEL=${{ inputs.label }}" >> $GITHUB_ENV
|
|
58
|
+
shell: bash
|
|
59
|
+
# Calling workflow:
|
|
60
|
+
# steps:
|
|
61
|
+
# - uses: ./my-action
|
|
62
|
+
# with: { label: "first" } # MY_LABEL = "first" ✓
|
|
63
|
+
# - uses: ./my-action
|
|
64
|
+
# with: { label: "second" } # MY_LABEL still "first" — silent bug!
|
|
65
|
+
- language: yaml
|
|
66
|
+
label: "CORRECT composite action — use GITHUB_OUTPUT"
|
|
67
|
+
code: |
|
|
68
|
+
# my-action/action.yml
|
|
69
|
+
inputs:
|
|
70
|
+
label:
|
|
71
|
+
required: true
|
|
72
|
+
outputs:
|
|
73
|
+
label:
|
|
74
|
+
description: "The resolved label"
|
|
75
|
+
value: ${{ steps.set.outputs.label }}
|
|
76
|
+
runs:
|
|
77
|
+
using: composite
|
|
78
|
+
steps:
|
|
79
|
+
- id: set
|
|
80
|
+
run: echo "label=${{ inputs.label }}" >> $GITHUB_OUTPUT
|
|
81
|
+
shell: bash
|
|
82
|
+
- language: yaml
|
|
83
|
+
label: "CORRECT calling workflow — propagate to env if needed"
|
|
84
|
+
code: |
|
|
85
|
+
steps:
|
|
86
|
+
- id: first
|
|
87
|
+
uses: ./my-action
|
|
88
|
+
with:
|
|
89
|
+
label: build-${{ github.sha }}
|
|
90
|
+
|
|
91
|
+
- id: second
|
|
92
|
+
uses: ./my-action
|
|
93
|
+
with:
|
|
94
|
+
label: deploy-${{ github.sha }}
|
|
95
|
+
|
|
96
|
+
- name: Use outputs from both calls
|
|
97
|
+
env:
|
|
98
|
+
FIRST_LABEL: ${{ steps.first.outputs.label }}
|
|
99
|
+
SECOND_LABEL: ${{ steps.second.outputs.label }}
|
|
100
|
+
run: |
|
|
101
|
+
echo "First: $FIRST_LABEL"
|
|
102
|
+
echo "Second: $SECOND_LABEL"
|
|
103
|
+
prevention:
|
|
104
|
+
- "Never use $GITHUB_ENV in composite actions that may be called more than once per job."
|
|
105
|
+
- "Migrate all composite action side effects from GITHUB_ENV to GITHUB_OUTPUT."
|
|
106
|
+
- "Test composite actions by calling them twice in the same job to catch this bug early."
|
|
107
|
+
- "Prefer outputs over env-var side effects for cleaner and predictable reuse semantics."
|
|
108
|
+
docs:
|
|
109
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#outputs-for-composite-actions"
|
|
110
|
+
label: "Outputs for composite actions"
|
|
111
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-an-environment-variable"
|
|
112
|
+
label: "Workflow commands — setting an environment variable"
|
|
113
|
+
- url: "https://github.com/actions/runner/issues/789"
|
|
114
|
+
label: "actions/runner#789 — GITHUB_ENV does not update when running composite actions multiple times"
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
id: runner-environment-034
|
|
2
|
+
title: "Azure PowerShell Az Module Upgraded from 14.x to 15.x on All Runner Images"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- azure
|
|
7
|
+
- powershell
|
|
8
|
+
- az-module
|
|
9
|
+
- breaking-change
|
|
10
|
+
- runner-image
|
|
11
|
+
- migration
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Az\\.(?:Accounts|Compute|Storage|Network|Resources|KeyVault|Sql|Monitor).*not recognized|The term '.*-Az.*' is not recognized"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "Install-Module.*Az.*RequiredVersion.*14"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "Get-AzVM|Set-AzVMExtension|New-AzResourceGroup.*parameter.*was not found|does not exist in the cmdlet"
|
|
18
|
+
flags: "i"
|
|
19
|
+
- regex: "CommandNotFoundException.*Az\\.|ParameterBindingException.*Az\\."
|
|
20
|
+
flags: "i"
|
|
21
|
+
error_messages:
|
|
22
|
+
- "The term 'Get-AzXxx' is not recognized as a name of a cmdlet"
|
|
23
|
+
- "A parameter cannot be found that matches parameter name"
|
|
24
|
+
- "Cannot process argument transformation on parameter"
|
|
25
|
+
- "Object reference not set to an instance of an object"
|
|
26
|
+
root_cause: |
|
|
27
|
+
The Azure PowerShell (Az) module was upgraded from version 14.6.0 to 15.6.1 on all
|
|
28
|
+
GitHub-hosted runner images beginning June 8, 2026 (completing June 15, 2026).
|
|
29
|
+
This is a major version bump (14 → 15) and includes breaking changes:
|
|
30
|
+
|
|
31
|
+
- **Removed cmdlets**: Several deprecated cmdlets from Az 14.x were dropped with no
|
|
32
|
+
backward-compatible alias.
|
|
33
|
+
- **Parameter changes**: Some cmdlets have altered parameter names, types, or
|
|
34
|
+
removed optional parameters that were previously accepted.
|
|
35
|
+
- **Output type changes**: Return objects from certain cmdlets have changed shape,
|
|
36
|
+
breaking pipeline expressions like `(Get-AzXxx).Property`.
|
|
37
|
+
- **ARM64 images affected disproportionately**: windows-11-arm64 and
|
|
38
|
+
windows-11-vs2026-arm64 jumped from Az 12.5.0 directly to 15.6.1 — a 3-major-version
|
|
39
|
+
leap with significantly more accumulated breaking changes.
|
|
40
|
+
|
|
41
|
+
Unlike other software installed on runner images, the Az PowerShell module does NOT
|
|
42
|
+
have an LTS version — only the latest release receives security fixes and support.
|
|
43
|
+
Microsoft discontinued support for Az 14.x as of the 15.x release cycle.
|
|
44
|
+
|
|
45
|
+
The runner images affected and their before/after versions:
|
|
46
|
+
- ubuntu-22.04, ubuntu-24.04, macos-14/15/26, windows-2022/2025/2025-vs2026: 14.6.0 → 15.6.1
|
|
47
|
+
- windows-11-arm64, windows-11-vs2026-arm64: 12.5.0 → 15.6.1
|
|
48
|
+
|
|
49
|
+
Source: actions/runner-images#14104
|
|
50
|
+
fix: |
|
|
51
|
+
**Option 1 — Pin to Az 14.6.0** (temporary fix while migrating):
|
|
52
|
+
Add an explicit Install-Module step at the start of any job using Az cmdlets.
|
|
53
|
+
|
|
54
|
+
**Option 2 — Migrate to Az 15.x** (recommended long-term):
|
|
55
|
+
Review the Az 15.x migration guide and update scripts to use the new cmdlet names,
|
|
56
|
+
parameters, and output types. Run `Get-AzVersion` to confirm the version in use.
|
|
57
|
+
|
|
58
|
+
**Diagnosis**: Use `Get-Module -Name Az.* -ListAvailable | Select Name, Version` to
|
|
59
|
+
confirm which Az module version is installed on the current runner.
|
|
60
|
+
fix_code:
|
|
61
|
+
- language: yaml
|
|
62
|
+
label: "Pin Az module to 14.6.0 for immediate rollback"
|
|
63
|
+
code: |
|
|
64
|
+
jobs:
|
|
65
|
+
deploy:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
steps:
|
|
68
|
+
- name: Pin Azure PowerShell to 14.6.0
|
|
69
|
+
shell: pwsh
|
|
70
|
+
run: |
|
|
71
|
+
Install-Module -Name Az -RequiredVersion 14.6.0 -Force -AllowClobber -Scope CurrentUser
|
|
72
|
+
Import-Module Az -RequiredVersion 14.6.0
|
|
73
|
+
|
|
74
|
+
- name: Azure login
|
|
75
|
+
uses: azure/login@v2
|
|
76
|
+
with:
|
|
77
|
+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
|
78
|
+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
|
79
|
+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
|
80
|
+
|
|
81
|
+
- name: Run Azure operations
|
|
82
|
+
shell: pwsh
|
|
83
|
+
run: |
|
|
84
|
+
# Your existing Az 14.x scripts
|
|
85
|
+
Get-AzVM -ResourceGroupName $env:RG_NAME
|
|
86
|
+
- language: yaml
|
|
87
|
+
label: "Verify installed Az version (diagnosis step)"
|
|
88
|
+
code: |
|
|
89
|
+
- name: Check Az module version
|
|
90
|
+
shell: pwsh
|
|
91
|
+
run: |
|
|
92
|
+
Get-Module -Name Az.Accounts -ListAvailable | Select-Object Name, Version
|
|
93
|
+
Get-AzVersion
|
|
94
|
+
prevention:
|
|
95
|
+
- "Subscribe to actions/runner-images announcements to get advance warning of Az module version changes."
|
|
96
|
+
- "Pin the Az module version explicitly in all CI workflows using `Install-Module -RequiredVersion` rather than relying on the runner image default."
|
|
97
|
+
- "Test Az-based workflows against the new version by temporarily installing Az 15.x before the runner image rollout date."
|
|
98
|
+
- "Review the Az 15.x migration guide (https://learn.microsoft.com/en-us/powershell/azure/migrate-az-15.0.0) when upgrading."
|
|
99
|
+
- "Use `Get-AzVersion` at the start of troublesome workflows to emit the exact Az version to logs."
|
|
100
|
+
docs:
|
|
101
|
+
- url: "https://github.com/actions/runner-images/issues/14104"
|
|
102
|
+
label: "runner-images#14104 — Az module update announcement (14.6.0 → 15.6.1)"
|
|
103
|
+
- url: "https://learn.microsoft.com/en-us/powershell/azure/migrate-az-15.0.0"
|
|
104
|
+
label: "Az 15.x migration guide"
|
|
105
|
+
- url: "https://learn.microsoft.com/en-us/powershell/azure/azureps-support-lifecycle"
|
|
106
|
+
label: "Az PowerShell support lifecycle"
|
|
107
|
+
- url: "https://learn.microsoft.com/en-us/powershell/azure/release-notes-azureps"
|
|
108
|
+
label: "Az PowerShell release notes"
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
id: runner-environment-035
|
|
2
|
+
title: "ubuntu-22.04 Pre-Cached Docker Images Removed — Cold Pulls Now Required"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: warning
|
|
5
|
+
tags:
|
|
6
|
+
- ubuntu
|
|
7
|
+
- docker
|
|
8
|
+
- pre-cached
|
|
9
|
+
- runner-image
|
|
10
|
+
- pull-rate
|
|
11
|
+
- performance
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Unable to find image '.*' locally"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "Pulling from (?:library/)?(?:node|python|postgres|redis|mysql|nginx|ubuntu|alpine).*\nDigest:"
|
|
16
|
+
flags: "im"
|
|
17
|
+
- regex: "docker: Error response from daemon.*manifest.*not found"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Unable to find image 'node:lts-alpine' locally"
|
|
21
|
+
- "Unable to find image 'postgres:15' locally"
|
|
22
|
+
- "Unable to find image 'redis:7-alpine' locally"
|
|
23
|
+
- "Pulling from library/node"
|
|
24
|
+
- "Pulling from library/postgres"
|
|
25
|
+
root_cause: |
|
|
26
|
+
Starting January 12, 2026, GitHub removed all pre-cached Docker images from the
|
|
27
|
+
ubuntu-22.04 runner image to address disk space limits. Previously, commonly used
|
|
28
|
+
Docker images (node, postgres, redis, mysql, python, nginx, alpine, etc.) were
|
|
29
|
+
pre-pulled into the runner's local Docker image cache, so `docker pull` or
|
|
30
|
+
`docker run` on those images started immediately without a network pull.
|
|
31
|
+
|
|
32
|
+
After the change, ALL Docker images must be pulled from the registry at runtime.
|
|
33
|
+
This has two main effects:
|
|
34
|
+
|
|
35
|
+
1. **Longer job times**: Cold pulls for large images (node, python, postgres) add
|
|
36
|
+
30-120+ seconds per job depending on image size and registry speed.
|
|
37
|
+
|
|
38
|
+
2. **DockerHub rate limit exposure**: Workflows that previously never hit the
|
|
39
|
+
DockerHub anonymous pull rate limit (100 pulls/6h per IP) may now exceed it
|
|
40
|
+
once caching is removed — especially on parallel matrix jobs all pulling the
|
|
41
|
+
same image from the same runner subnet IP.
|
|
42
|
+
|
|
43
|
+
The change only affects ubuntu-22.04. Ubuntu 24.04 and other images were not
|
|
44
|
+
pre-caching Docker images in the same way.
|
|
45
|
+
|
|
46
|
+
Source: actions/runner-images#13472
|
|
47
|
+
fix: |
|
|
48
|
+
**Option 1 — Authenticate to DockerHub** to raise the rate limit to 200 pulls/6h
|
|
49
|
+
(free account) or unlimited (Pro/Team).
|
|
50
|
+
|
|
51
|
+
**Option 2 — Use GitHub Container Registry (ghcr.io)** for your own images —
|
|
52
|
+
these never count against DockerHub rate limits and pull faster from GitHub runners.
|
|
53
|
+
|
|
54
|
+
**Option 3 — Upgrade to ubuntu-24.04** if the workflow is not tied to ubuntu-22.04
|
|
55
|
+
specific packages. The 24.04 image has current tooling and better long-term support.
|
|
56
|
+
|
|
57
|
+
**Option 4 — Add a docker/login-action step** before any Docker operations to
|
|
58
|
+
authenticate even with a free DockerHub account, which 2x the anonymous rate limit.
|
|
59
|
+
fix_code:
|
|
60
|
+
- language: yaml
|
|
61
|
+
label: "Authenticate to DockerHub to avoid rate limits on ubuntu-22.04"
|
|
62
|
+
code: |
|
|
63
|
+
jobs:
|
|
64
|
+
build:
|
|
65
|
+
runs-on: ubuntu-22.04
|
|
66
|
+
steps:
|
|
67
|
+
- name: Log in to DockerHub
|
|
68
|
+
uses: docker/login-action@v3
|
|
69
|
+
with:
|
|
70
|
+
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
71
|
+
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
72
|
+
|
|
73
|
+
- name: Run tests with Postgres
|
|
74
|
+
run: |
|
|
75
|
+
docker run --rm -d -p 5432:5432 \
|
|
76
|
+
-e POSTGRES_PASSWORD=test \
|
|
77
|
+
postgres:15-alpine
|
|
78
|
+
- language: yaml
|
|
79
|
+
label: "Use GitHub Container Registry instead of DockerHub"
|
|
80
|
+
code: |
|
|
81
|
+
jobs:
|
|
82
|
+
build:
|
|
83
|
+
runs-on: ubuntu-22.04
|
|
84
|
+
permissions:
|
|
85
|
+
packages: read
|
|
86
|
+
steps:
|
|
87
|
+
- name: Log in to GHCR
|
|
88
|
+
uses: docker/login-action@v3
|
|
89
|
+
with:
|
|
90
|
+
registry: ghcr.io
|
|
91
|
+
username: ${{ github.actor }}
|
|
92
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
93
|
+
|
|
94
|
+
- name: Run service container from GHCR
|
|
95
|
+
run: docker run --rm ghcr.io/myorg/my-image:latest
|
|
96
|
+
prevention:
|
|
97
|
+
- "Authenticate to DockerHub in all workflows that pull images on ubuntu-22.04 runners."
|
|
98
|
+
- "Consider migrating ubuntu-22.04 workflows to ubuntu-24.04 — it's the current default and avoids this issue."
|
|
99
|
+
- "Prefer GitHub Container Registry (ghcr.io) for your own images to eliminate DockerHub rate limits entirely."
|
|
100
|
+
- "Use `docker/login-action` as a standard first step in any workflow that involves Docker image pulls."
|
|
101
|
+
- "Monitor job durations after runner image updates — unexpected slowdowns often signal a caching change."
|
|
102
|
+
docs:
|
|
103
|
+
- url: "https://github.com/actions/runner-images/issues/13472"
|
|
104
|
+
label: "runner-images#13472 — ubuntu-22.04 pre-cached Docker images removal announcement"
|
|
105
|
+
- url: "https://github.com/actions/runner-images/issues/1445"
|
|
106
|
+
label: "runner-images#1445 — DockerHub pull rate limit discussion"
|
|
107
|
+
- url: "https://docs.docker.com/docker-hub/download-rate-limit/"
|
|
108
|
+
label: "DockerHub pull rate limits documentation"
|
|
109
|
+
- url: "https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry"
|
|
110
|
+
label: "Working with GitHub Container Registry (ghcr.io)"
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
id: runner-environment-036
|
|
2
|
+
title: "Windows MSVC LTCG Linker Fails When Jobs in Same Run Receive Different Image Versions"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- windows
|
|
7
|
+
- msvc
|
|
8
|
+
- ltcg
|
|
9
|
+
- linker
|
|
10
|
+
- runner-image
|
|
11
|
+
- build
|
|
12
|
+
- vs2026
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: "C1900.*Il mismatch between 'P1' version.*and 'P2' version"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "LNK1257.*code generation failed"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "LNK2001.*unresolved external symbol __std_"
|
|
19
|
+
flags: "i"
|
|
20
|
+
- regex: "fatal error C1900"
|
|
21
|
+
flags: "i"
|
|
22
|
+
error_messages:
|
|
23
|
+
- "EXEC : fatal error C1900: Il mismatch between 'P1' version '20251208' and 'P2' version '20250730'"
|
|
24
|
+
- "LINK : fatal error LNK1257: code generation failed"
|
|
25
|
+
- "skia.lib(...) : error LNK2001: unresolved external symbol __std_minmax_element_f_"
|
|
26
|
+
- "skia.lib(...) : error LNK2001: unresolved external symbol __std_max_element_2u"
|
|
27
|
+
root_cause: |
|
|
28
|
+
GitHub Actions does not guarantee that all jobs within a single workflow run receive
|
|
29
|
+
the same concrete runner image version. When a runner image is being rolled out
|
|
30
|
+
(e.g., during a VS 2026 update or patch cycle), some jobs may be assigned to runners
|
|
31
|
+
with image version 20260510.103.1 while others receive 20260518.113.1 — even for
|
|
32
|
+
jobs with the same `runs-on: windows-2025-vs2026` label.
|
|
33
|
+
|
|
34
|
+
This becomes a hard build failure for workflows that use MSVC Link-Time Code
|
|
35
|
+
Generation (LTCG / /GL):
|
|
36
|
+
- Job A (build) compiles .obj and .lib files with /GL on image version X
|
|
37
|
+
→ MSVC compiler emits Intermediate Language (IL) artifacts stamped with version X.
|
|
38
|
+
- Job B (link) downloads those artifacts and attempts to link them using /LTCG on
|
|
39
|
+
image version Y (a different MSVC toolset patch version).
|
|
40
|
+
- MSVC detects the IL version mismatch (C1900) and aborts: the IL produced by
|
|
41
|
+
one toolset cannot be consumed by a different toolset version.
|
|
42
|
+
|
|
43
|
+
The same failure can occur for any build pipeline that:
|
|
44
|
+
- Splits compilation and linking across separate jobs
|
|
45
|
+
- Uses artifact upload/download to pass compiled objects between jobs
|
|
46
|
+
- Relies on LTCG, MSVC STL internals, or other IL-sensitive optimizations
|
|
47
|
+
|
|
48
|
+
This is a known limitation of the GitHub Actions runner assignment model — image
|
|
49
|
+
rollouts are gradual across the runner pool, and job assignments are not pinned
|
|
50
|
+
to a specific image version within a run.
|
|
51
|
+
|
|
52
|
+
Source: actions/runner-images#14140
|
|
53
|
+
fix: |
|
|
54
|
+
**Option 1 — Combine compile and link in a single job** so all MSVC operations
|
|
55
|
+
run on the same runner image. This eliminates the cross-image artifact transfer.
|
|
56
|
+
|
|
57
|
+
**Option 2 — Disable LTCG for CI builds** (/GL is a release optimization; most
|
|
58
|
+
CI builds only need it for final release binaries, not PRs or branch builds).
|
|
59
|
+
|
|
60
|
+
**Option 3 — Add MSVC toolset version check** in the link job to detect mismatches
|
|
61
|
+
before the link fails, and fail fast with a clear diagnostic message.
|
|
62
|
+
|
|
63
|
+
**Option 4 — Use a self-hosted runner** with a pinned image version for builds
|
|
64
|
+
that require LTCG across jobs.
|
|
65
|
+
fix_code:
|
|
66
|
+
- language: yaml
|
|
67
|
+
label: "Combine compile and link in one job to avoid image mismatch"
|
|
68
|
+
code: |
|
|
69
|
+
jobs:
|
|
70
|
+
build-and-link:
|
|
71
|
+
runs-on: windows-2025-vs2026
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/checkout@v4
|
|
74
|
+
|
|
75
|
+
- name: Configure CMake
|
|
76
|
+
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
|
|
77
|
+
|
|
78
|
+
# Compile AND link in the same job — same image version guaranteed
|
|
79
|
+
- name: Build (compile + link)
|
|
80
|
+
run: cmake --build build --config Release
|
|
81
|
+
- language: yaml
|
|
82
|
+
label: "Disable LTCG on CI builds, enable only for release"
|
|
83
|
+
code: |
|
|
84
|
+
jobs:
|
|
85
|
+
build:
|
|
86
|
+
runs-on: windows-2025-vs2026
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/checkout@v4
|
|
89
|
+
|
|
90
|
+
- name: Build without LTCG (CI)
|
|
91
|
+
# Pass -DNO_LTCG=1 or equivalent CMake flag to disable /GL and /LTCG
|
|
92
|
+
# LTCG is only enabled in the separate release workflow
|
|
93
|
+
run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DENABLE_LTCG=OFF
|
|
94
|
+
env:
|
|
95
|
+
# Alternatively: override CFLAGS/CXXFLAGS directly
|
|
96
|
+
CFLAGS: /O2 # Optimize without /GL
|
|
97
|
+
CXXFLAGS: /O2
|
|
98
|
+
prevention:
|
|
99
|
+
- "Never split MSVC compilation and LTCG linking across separate jobs that upload/download artifacts."
|
|
100
|
+
- "Disable /GL (LTCG) in CI builds — reserve it for final release artifact builds on a single job."
|
|
101
|
+
- "Monitor runner image rollout announcements; if a new Windows image version is being deployed, LTCG cross-job builds are at risk during the rollout window."
|
|
102
|
+
- "Add a MSVC version check step at the start of link jobs to emit the toolset version to logs for post-failure diagnosis."
|
|
103
|
+
- "Use self-hosted runners with pinned image versions for release builds that require LTCG across jobs."
|
|
104
|
+
docs:
|
|
105
|
+
- url: "https://github.com/actions/runner-images/issues/14140"
|
|
106
|
+
label: "runner-images#14140 — MSVC LTCG linker failure from mixed image versions in same run"
|
|
107
|
+
- url: "https://learn.microsoft.com/en-us/cpp/build/reference/gl-whole-program-optimization"
|
|
108
|
+
label: "MSVC /GL (Whole Program Optimization) documentation"
|
|
109
|
+
- url: "https://learn.microsoft.com/en-us/cpp/build/reference/ltcg-link-time-code-generation"
|
|
110
|
+
label: "MSVC /LTCG (Link-Time Code Generation) documentation"
|
|
111
|
+
- url: "https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners"
|
|
112
|
+
label: "About GitHub-hosted runners"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
id: silent-failures-017
|
|
2
|
+
title: "iOS App Store Upload Fails Silently — macOS-latest Uses Xcode 16.4 but Apple Requires iOS 26 SDK"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- macos
|
|
7
|
+
- xcode
|
|
8
|
+
- ios
|
|
9
|
+
- app-store
|
|
10
|
+
- sdk
|
|
11
|
+
- silent-failure
|
|
12
|
+
- apple
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: "built with the iOS (?:18|17|16)\\.\\d SDK"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "must be built with the iOS 26 SDK or later.*App Store"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "Invalid Toolchain.*iOS.*SDK.*required"
|
|
19
|
+
flags: "i"
|
|
20
|
+
- regex: "ITMS-90725|ITMS-90206"
|
|
21
|
+
flags: "i"
|
|
22
|
+
error_messages:
|
|
23
|
+
- "This app was built with the iOS 18.5 SDK. All iOS and iPadOS apps must be built with the iOS 26 SDK or later in order to be uploaded to App Store Connect."
|
|
24
|
+
- "ERROR ITMS-90725: SDK Version Issue - This app was built with the iOS 18.5 SDK"
|
|
25
|
+
- "Invalid Toolchain. New apps and app updates must be built with the public iOS 26 SDK or later"
|
|
26
|
+
root_cause: |
|
|
27
|
+
Apple requires that all new iOS/iPadOS App Store submissions be built with the
|
|
28
|
+
iOS 26 SDK or later (enforced starting mid-2026). However, `macos-latest` currently
|
|
29
|
+
points to macOS 15 (until the macOS 26 migration completes in July 2026), and
|
|
30
|
+
macOS 15 runner images ship with Xcode 16.4 as the default — which includes the
|
|
31
|
+
iOS 18.5 SDK, NOT the iOS 26 SDK.
|
|
32
|
+
|
|
33
|
+
The failure is a classic silent-failure pattern:
|
|
34
|
+
- The build step SUCCEEDS with zero errors or warnings.
|
|
35
|
+
- The archive step SUCCEEDS.
|
|
36
|
+
- The upload/export to App Store Connect FAILS with the SDK version error.
|
|
37
|
+
- Because the failure occurs at the distribution stage (not compilation), it
|
|
38
|
+
can be misdiagnosed as a signing, provisioning, or network issue.
|
|
39
|
+
|
|
40
|
+
Workflows that use `uses: maxim-lobanov/setup-xcode` with an explicit 16.x version,
|
|
41
|
+
or that rely on `macos-latest` defaults without explicit Xcode pinning, will
|
|
42
|
+
silently produce an .ipa or .xcarchive that cannot be submitted to App Store Connect.
|
|
43
|
+
|
|
44
|
+
After the `macos-latest` migration to macOS 26 completes (July 15, 2026), the default
|
|
45
|
+
Xcode will be 26.4.1+, which includes the iOS 26 SDK and resolves this issue.
|
|
46
|
+
Until then, explicit Xcode pinning is required.
|
|
47
|
+
|
|
48
|
+
Source: actions/runner-images#14165
|
|
49
|
+
fix: |
|
|
50
|
+
**Immediate fix**: Pin Xcode to 26.x explicitly using `maxim-lobanov/setup-xcode`
|
|
51
|
+
before any build or archive steps. Use `macos-26` as the runner to ensure Xcode 26
|
|
52
|
+
is available.
|
|
53
|
+
|
|
54
|
+
**For teams not ready to migrate to Xcode 26**: Use `macos-26` and pin to Xcode 26.4.1
|
|
55
|
+
or later — this is required by Apple regardless of code changes.
|
|
56
|
+
|
|
57
|
+
**Verify the SDK in use** by adding `xcodebuild -showsdks` before the build step.
|
|
58
|
+
fix_code:
|
|
59
|
+
- language: yaml
|
|
60
|
+
label: "Pin to macOS 26 + Xcode 26 for App Store submissions"
|
|
61
|
+
code: |
|
|
62
|
+
jobs:
|
|
63
|
+
release:
|
|
64
|
+
runs-on: macos-26 # Explicit: don't rely on macos-latest during transition
|
|
65
|
+
steps:
|
|
66
|
+
- uses: actions/checkout@v4
|
|
67
|
+
|
|
68
|
+
# Explicitly select Xcode 26 (includes iOS 26 SDK required by App Store)
|
|
69
|
+
- uses: maxim-lobanov/setup-xcode@v1
|
|
70
|
+
with:
|
|
71
|
+
xcode-version: '26.4' # or '26.5' once generally available
|
|
72
|
+
|
|
73
|
+
- name: Verify SDK
|
|
74
|
+
run: xcodebuild -showsdks | grep -i iphoneos
|
|
75
|
+
|
|
76
|
+
- name: Archive
|
|
77
|
+
run: |
|
|
78
|
+
xcodebuild archive \
|
|
79
|
+
-scheme MyApp \
|
|
80
|
+
-archivePath $RUNNER_TEMP/MyApp.xcarchive \
|
|
81
|
+
-destination generic/platform=iOS
|
|
82
|
+
|
|
83
|
+
- name: Export for App Store
|
|
84
|
+
run: |
|
|
85
|
+
xcodebuild -exportArchive \
|
|
86
|
+
-archivePath $RUNNER_TEMP/MyApp.xcarchive \
|
|
87
|
+
-exportOptionsPlist ExportOptions.plist \
|
|
88
|
+
-exportPath $RUNNER_TEMP/MyApp.ipa
|
|
89
|
+
- language: yaml
|
|
90
|
+
label: "Check installed Xcode versions and default SDK (diagnosis)"
|
|
91
|
+
code: |
|
|
92
|
+
- name: Diagnose Xcode and SDK versions
|
|
93
|
+
run: |
|
|
94
|
+
echo "=== Default Xcode ==="
|
|
95
|
+
xcode-select -p
|
|
96
|
+
xcodebuild -version
|
|
97
|
+
echo "=== Available SDKs ==="
|
|
98
|
+
xcodebuild -showsdks | grep -i iphone
|
|
99
|
+
prevention:
|
|
100
|
+
- "Never rely on `macos-latest` default Xcode for App Store release builds — always pin the Xcode version explicitly."
|
|
101
|
+
- "Use `xcodebuild -showsdks` as an early workflow step to emit the active iOS SDK version to logs."
|
|
102
|
+
- "Run a smoke test of the App Store export (not just the build) in CI to catch SDK requirement errors before manual submission."
|
|
103
|
+
- "Subscribe to Apple Developer news for SDK requirement enforcement dates and plan runner image migrations in advance."
|
|
104
|
+
- "Pin `runs-on: macos-26` explicitly rather than `macos-latest` for release workflows during the macOS version transition period."
|
|
105
|
+
docs:
|
|
106
|
+
- url: "https://github.com/actions/runner-images/issues/14165"
|
|
107
|
+
label: "runner-images#14165 — macOS-latest Xcode 16.4 breaks App Store submissions"
|
|
108
|
+
- url: "https://github.com/actions/runner-images/issues/14167"
|
|
109
|
+
label: "runner-images#14167 — macos-latest will use macos-26 in June 2026"
|
|
110
|
+
- url: "https://github.com/maxim-lobanov/setup-xcode"
|
|
111
|
+
label: "maxim-lobanov/setup-xcode — Pin Xcode version in workflows"
|
|
112
|
+
- url: "https://developer.apple.com/news/releases/"
|
|
113
|
+
label: "Apple Developer News — SDK requirement announcements"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
id: silent-failures-016
|
|
2
|
+
title: "failure() Returns false After continue-on-error Step"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- continue-on-error
|
|
7
|
+
- failure
|
|
8
|
+
- status-functions
|
|
9
|
+
- error-handler
|
|
10
|
+
- silent-failure
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "continue-on-error:\\s*true"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "if:\\s*(\\$\\{\\{\\s*)?failure\\(\\)"
|
|
15
|
+
flags: "i"
|
|
16
|
+
error_messages:
|
|
17
|
+
- "Skipping step: due to a failed condition"
|
|
18
|
+
root_cause: |
|
|
19
|
+
When a step has `continue-on-error: true` and that step fails, GitHub Actions
|
|
20
|
+
records the step outcome as failure but immediately converts the job aggregate
|
|
21
|
+
status back to success before evaluating the next step. This means that
|
|
22
|
+
failure() in a subsequent step's if condition evaluates to false — the runner
|
|
23
|
+
sees the job as currently-successful, not currently-failed.
|
|
24
|
+
|
|
25
|
+
The continue-on-error flag was designed to allow a job to proceed past a failing
|
|
26
|
+
step, but the side effect is that it consumes the failure signal from all
|
|
27
|
+
status-check functions. Developers commonly write error-handler steps using
|
|
28
|
+
if: failure(), expecting them to trigger when the prior step fails, but this
|
|
29
|
+
pattern silently breaks when continue-on-error: true is present on that prior step.
|
|
30
|
+
|
|
31
|
+
The step individual outcome context (steps.<id>.outcome) is the only reliable
|
|
32
|
+
way to detect the specific step result after continue-on-error.
|
|
33
|
+
fix: |
|
|
34
|
+
Replace if: failure() with if: steps.<step_id>.outcome == 'failure' when
|
|
35
|
+
writing error handlers for specific steps that use continue-on-error: true.
|
|
36
|
+
The steps.<id>.outcome context holds the actual step result regardless of
|
|
37
|
+
job-level status modifications.
|
|
38
|
+
fix_code:
|
|
39
|
+
- language: yaml
|
|
40
|
+
label: "WRONG — failure() never triggers after continue-on-error"
|
|
41
|
+
code: |
|
|
42
|
+
steps:
|
|
43
|
+
- name: Risky operation
|
|
44
|
+
id: risky
|
|
45
|
+
continue-on-error: true
|
|
46
|
+
run: ./might-fail.sh
|
|
47
|
+
|
|
48
|
+
- name: Handle failure
|
|
49
|
+
if: failure() # NEVER RUNS — job status was reset to success
|
|
50
|
+
run: ./notify-on-failure.sh
|
|
51
|
+
- language: yaml
|
|
52
|
+
label: "CORRECT — check specific step outcome"
|
|
53
|
+
code: |
|
|
54
|
+
steps:
|
|
55
|
+
- name: Risky operation
|
|
56
|
+
id: risky
|
|
57
|
+
continue-on-error: true
|
|
58
|
+
run: ./might-fail.sh
|
|
59
|
+
|
|
60
|
+
- name: Handle failure
|
|
61
|
+
if: steps.risky.outcome == 'failure'
|
|
62
|
+
run: ./notify-on-failure.sh
|
|
63
|
+
|
|
64
|
+
- name: Continue regardless
|
|
65
|
+
run: echo "Job continues either way"
|
|
66
|
+
- language: yaml
|
|
67
|
+
label: "CORRECT — combine with always() for comprehensive guards"
|
|
68
|
+
code: |
|
|
69
|
+
steps:
|
|
70
|
+
- name: Risky operation
|
|
71
|
+
id: risky
|
|
72
|
+
continue-on-error: true
|
|
73
|
+
run: ./might-fail.sh
|
|
74
|
+
|
|
75
|
+
- name: Handle step failure
|
|
76
|
+
if: always() && steps.risky.outcome == 'failure'
|
|
77
|
+
run: |
|
|
78
|
+
echo "::warning::Risky operation failed"
|
|
79
|
+
echo "## Failed" >> $GITHUB_STEP_SUMMARY
|
|
80
|
+
prevention:
|
|
81
|
+
- "Never use if: failure() as handler for a step with continue-on-error: true — use steps.<id>.outcome."
|
|
82
|
+
- "Reserve failure() for handlers that should trigger on any upstream job failure."
|
|
83
|
+
- "Add step IDs to all continue-on-error steps so outcome checking is always possible."
|
|
84
|
+
docs:
|
|
85
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-conditions-to-control-job-execution"
|
|
86
|
+
label: "Using conditions to control job execution"
|
|
87
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error"
|
|
88
|
+
label: "continue-on-error workflow syntax"
|
|
89
|
+
- url: "https://github.com/actions/toolkit/issues/1034"
|
|
90
|
+
label: "actions/toolkit#1034 — Wrong behaviour combining continue-on-error and failure()"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
id: silent-failures-015
|
|
2
|
+
title: "Mixing ${{ }} Interpolation Inside if: Condition Causes Step to Always Run"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- if-condition
|
|
7
|
+
- expression
|
|
8
|
+
- interpolation
|
|
9
|
+
- always-runs
|
|
10
|
+
- silent-failure
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "if:.*==.*'[^']*\\$\\{\\{[^}]+\\}\\}[^']*'"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "if:.*'[^']*\\$\\{\\{[^}]+\\}\\}[^']*'.*=="
|
|
15
|
+
flags: "i"
|
|
16
|
+
error_messages:
|
|
17
|
+
- "step runs unexpectedly even when condition should be false"
|
|
18
|
+
- "if condition always evaluates true"
|
|
19
|
+
root_cause: |
|
|
20
|
+
When the `if:` field contains BOTH bare comparison operators AND a `${{ }}`
|
|
21
|
+
interpolation fragment within a quoted string (e.g.,
|
|
22
|
+
`if: github.ref == 'refs/heads/${{ env.BRANCH }}'`), GitHub Actions wraps the
|
|
23
|
+
entire bare-string value in `${{ ... }}` for expression evaluation. This makes
|
|
24
|
+
the outer expression evaluate the literal string
|
|
25
|
+
`"github.ref == 'refs/heads/main'"` — a non-empty string — which is always truthy.
|
|
26
|
+
|
|
27
|
+
The runner does not parse the embedded `${{ }}` fragment as an inner expression;
|
|
28
|
+
it only processes the outer auto-wrap. The result is that the step runs on every
|
|
29
|
+
trigger regardless of the actual comparison result — a textbook silent failure
|
|
30
|
+
since the workflow file looks syntactically correct and no error is emitted.
|
|
31
|
+
fix: |
|
|
32
|
+
Wrap the ENTIRE `if:` condition in `${{ }}` so the runner treats it as an
|
|
33
|
+
expression from the start. Use the `format()` function or direct context
|
|
34
|
+
comparisons instead of embedding `${{ }}` inside quoted strings.
|
|
35
|
+
fix_code:
|
|
36
|
+
- language: yaml
|
|
37
|
+
label: "WRONG — mixed interpolation, step always runs"
|
|
38
|
+
code: |
|
|
39
|
+
env:
|
|
40
|
+
TARGET_BRANCH: main
|
|
41
|
+
steps:
|
|
42
|
+
- name: Deploy to production
|
|
43
|
+
# This ALWAYS runs — the string is truthy regardless of branch
|
|
44
|
+
if: github.ref == 'refs/heads/${{ env.TARGET_BRANCH }}'
|
|
45
|
+
run: ./deploy.sh
|
|
46
|
+
- language: yaml
|
|
47
|
+
label: "CORRECT — wrap entire condition in ${{ }}"
|
|
48
|
+
code: |
|
|
49
|
+
env:
|
|
50
|
+
TARGET_BRANCH: main
|
|
51
|
+
steps:
|
|
52
|
+
- name: Deploy to production
|
|
53
|
+
if: ${{ github.ref == format('refs/heads/{0}', env.TARGET_BRANCH) }}
|
|
54
|
+
run: ./deploy.sh
|
|
55
|
+
- language: yaml
|
|
56
|
+
label: "CORRECT — direct string literal comparison"
|
|
57
|
+
code: |
|
|
58
|
+
steps:
|
|
59
|
+
- name: Deploy to production
|
|
60
|
+
if: ${{ github.ref == 'refs/heads/main' }}
|
|
61
|
+
run: ./deploy.sh
|
|
62
|
+
prevention:
|
|
63
|
+
- "Never embed `${{ }}` fragments inside quoted strings within `if:` — always wrap the entire condition."
|
|
64
|
+
- "Lint workflows with `actionlint` — it flags mixed expression/literal patterns in `if:` fields."
|
|
65
|
+
- "Prefer `format()` or direct context references over string interpolation in conditionals."
|
|
66
|
+
- "Test conditions on branches that should NOT match to verify they don't run."
|
|
67
|
+
docs:
|
|
68
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions"
|
|
69
|
+
label: "Evaluate expressions in workflows and actions"
|
|
70
|
+
- url: "https://github.com/actions/runner/issues/1173"
|
|
71
|
+
label: "actions/runner#1173 — If condition always evaluated as true when containing ${{ }} inside"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
id: yaml-syntax-018
|
|
2
|
+
title: "Job if: Condition Skipped When Needed Job Is Skipped — Requires always() &&"
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- needs
|
|
7
|
+
- skipped
|
|
8
|
+
- if-condition
|
|
9
|
+
- always
|
|
10
|
+
- job-level
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "needs.*result.*skipped"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "if:.*needs\\.[a-z_-]+\\.outputs"
|
|
15
|
+
flags: "i"
|
|
16
|
+
error_messages:
|
|
17
|
+
- "Skipping job: due to job needs not meeting conditions"
|
|
18
|
+
- "Job was skipped"
|
|
19
|
+
root_cause: |
|
|
20
|
+
When a job declares `needs: [job_a, job_b]` and any of those upstream jobs is in
|
|
21
|
+
a `skipped` state, GitHub Actions propagates the skip to the dependent job by
|
|
22
|
+
default — even when that job has its own `if:` condition that evaluates to `true`.
|
|
23
|
+
|
|
24
|
+
GitHub Actions imposes an implicit rule: all `needs` jobs must have
|
|
25
|
+
`result == 'success'` unless the dependent job's condition explicitly overrides
|
|
26
|
+
the check. A skipped upstream job does NOT satisfy the implicit success requirement.
|
|
27
|
+
The `if:` condition is NOT evaluated as a full override — it is only evaluated
|
|
28
|
+
AFTER the implicit needs-success check passes, so if that check fails the `if:`
|
|
29
|
+
is never reached.
|
|
30
|
+
|
|
31
|
+
The fix is `always() &&` at the start of the `if:` condition, which bypasses the
|
|
32
|
+
implicit needs-success requirement and forces the runner to evaluate the full condition.
|
|
33
|
+
fix: |
|
|
34
|
+
Prepend `always() &&` to any job-level `if:` where an upstream `needs` job may be
|
|
35
|
+
legitimately skipped. Alternatively, explicitly allow skipped results:
|
|
36
|
+
`needs.job_name.result == 'success' || needs.job_name.result == 'skipped'`.
|
|
37
|
+
fix_code:
|
|
38
|
+
- language: yaml
|
|
39
|
+
label: "WRONG — deploy skipped even though if evaluates true"
|
|
40
|
+
code: |
|
|
41
|
+
jobs:
|
|
42
|
+
build:
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
outputs:
|
|
45
|
+
should_deploy: ${{ steps.check.outputs.deploy }}
|
|
46
|
+
steps:
|
|
47
|
+
- id: check
|
|
48
|
+
run: echo "deploy=true" >> $GITHUB_OUTPUT
|
|
49
|
+
|
|
50
|
+
integration-tests:
|
|
51
|
+
needs: build
|
|
52
|
+
if: ${{ needs.build.outputs.should_deploy == 'false' }}
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
steps:
|
|
55
|
+
- run: echo "optional integration tests"
|
|
56
|
+
|
|
57
|
+
deploy:
|
|
58
|
+
needs: [build, integration-tests]
|
|
59
|
+
# PROBLEM: integration-tests was skipped → deploy is also skipped
|
|
60
|
+
# even though the condition below is true
|
|
61
|
+
if: ${{ needs.build.outputs.should_deploy == 'true' }}
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
steps:
|
|
64
|
+
- run: ./deploy.sh
|
|
65
|
+
- language: yaml
|
|
66
|
+
label: "CORRECT — prepend always() to force evaluation"
|
|
67
|
+
code: |
|
|
68
|
+
jobs:
|
|
69
|
+
deploy:
|
|
70
|
+
needs: [build, integration-tests]
|
|
71
|
+
if: always() && needs.build.outputs.should_deploy == 'true'
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
steps:
|
|
74
|
+
- run: ./deploy.sh
|
|
75
|
+
- language: yaml
|
|
76
|
+
label: "CORRECT — explicit skipped result check"
|
|
77
|
+
code: |
|
|
78
|
+
jobs:
|
|
79
|
+
deploy:
|
|
80
|
+
needs: [build, integration-tests]
|
|
81
|
+
if: |
|
|
82
|
+
always() &&
|
|
83
|
+
needs.build.result == 'success' &&
|
|
84
|
+
(needs.integration-tests.result == 'success' ||
|
|
85
|
+
needs.integration-tests.result == 'skipped')
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
steps:
|
|
88
|
+
- run: ./deploy.sh
|
|
89
|
+
prevention:
|
|
90
|
+
- "Add `always() &&` to any job `if:` condition where upstream `needs` jobs may be skipped."
|
|
91
|
+
- "Treat a skipped upstream need as equivalent to a failed need unless `always()` is used."
|
|
92
|
+
- "Map your workflow DAG — anywhere a branch is optionally skipped, downstream jobs need `always()`."
|
|
93
|
+
- "Use `actionlint` to surface potential needs/if interaction issues statically."
|
|
94
|
+
docs:
|
|
95
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idif"
|
|
96
|
+
label: "jobs.<job_id>.if — Workflow syntax"
|
|
97
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-conditions-to-control-job-execution#using-status-check-functions"
|
|
98
|
+
label: "Using status check functions (always, failure, success, cancelled)"
|
|
99
|
+
- url: "https://github.com/actions/runner/issues/491"
|
|
100
|
+
label: "actions/runner#491 — Job if condition not evaluated correctly if needs job is skipped"
|
package/package.json
CHANGED