@htekdev/actions-debugger 1.0.19 → 1.0.21
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/permissions-auth/fine-grained-pat-org-restricted-forbidden.yml +104 -0
- package/errors/permissions-auth/pat-sso-org-not-authorized-checkout-404.yml +107 -0
- package/errors/runner-environment/android-sdk-api37-hash-mismatch.yml +97 -0
- package/errors/runner-environment/android-sdk-platforms-v33-removed.yml +120 -0
- package/errors/runner-environment/docker-buildx-matrix-platform-tag-overwrite.yml +143 -0
- package/errors/runner-environment/node24-action-self-hosted-runner-too-old.yml +93 -0
- package/errors/runner-environment/python39-removed-runner-images.yml +97 -0
- package/errors/silent-failures/docker-run-pipe-tee-exit-code-zero.yml +99 -0
- package/package.json +1 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
id: permissions-auth-024
|
|
2
|
+
title: "Fine-Grained PATs Blocked by Organization Policy — 'forbidden from accessing this repository'"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- fine-grained-pat
|
|
7
|
+
- organization-policy
|
|
8
|
+
- personal-access-token
|
|
9
|
+
- authentication
|
|
10
|
+
- 403
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "Fine-grained personal access tokens are forbidden from accessing this repository"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "fine.grained.*forbidden|forbidden.*fine.grained"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "personal access tokens.*not permitted|PAT.*restricted"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "remote: Fine-grained personal access tokens are forbidden from accessing this repository."
|
|
20
|
+
- "fatal: unable to access 'https://github.com/org/repo.git/': The requested URL returned error: 403"
|
|
21
|
+
- "Fine-grained personal access tokens are forbidden from accessing this repository."
|
|
22
|
+
root_cause: |
|
|
23
|
+
GitHub organizations can restrict which types of PATs are allowed to access their
|
|
24
|
+
resources. When an organization enables "Restrict access via fine-grained personal
|
|
25
|
+
access tokens" (or sets PAT policy to "Disable fine-grained personal access tokens"),
|
|
26
|
+
any fine-grained PAT — regardless of its scopes or permissions — will be rejected
|
|
27
|
+
with a 403 error containing the message "Fine-grained personal access tokens are
|
|
28
|
+
forbidden from accessing this repository."
|
|
29
|
+
|
|
30
|
+
This policy applies even if:
|
|
31
|
+
- The fine-grained PAT has all required permissions (contents: write, etc.)
|
|
32
|
+
- The PAT was created by an org owner
|
|
33
|
+
- The repository is public
|
|
34
|
+
- The user is an org admin
|
|
35
|
+
|
|
36
|
+
Common confusion: developers who switch from classic PATs to fine-grained PATs
|
|
37
|
+
for better security/granularity hit this org-level block and cannot understand
|
|
38
|
+
why a seemingly correct token still fails with 403.
|
|
39
|
+
|
|
40
|
+
Sources: stackoverflow.com/questions/79471500,
|
|
41
|
+
GitHub Docs on PAT policies for organizations
|
|
42
|
+
fix: |
|
|
43
|
+
Option 1 (Immediate fix): Use a classic PAT with the required scopes instead of
|
|
44
|
+
a fine-grained PAT. Classic PATs are not subject to the fine-grained restriction.
|
|
45
|
+
|
|
46
|
+
Option 2 (Organization change — requires admin): Have an org admin update the
|
|
47
|
+
organization's PAT policy to allow fine-grained PATs:
|
|
48
|
+
Settings → Developer Settings → Personal access token policy
|
|
49
|
+
→ Allow fine-grained personal access tokens
|
|
50
|
+
→ (optionally require approval for each token)
|
|
51
|
+
|
|
52
|
+
Option 3 (Recommended long-term): Replace the PAT with a GitHub App token.
|
|
53
|
+
GitHub App tokens are not subject to org-level PAT restrictions and provide
|
|
54
|
+
better auditability and short-lived credentials.
|
|
55
|
+
fix_code:
|
|
56
|
+
- language: yaml
|
|
57
|
+
label: "Use classic PAT (immediate workaround)"
|
|
58
|
+
code: |
|
|
59
|
+
# Replace the fine-grained PAT secret with a classic PAT:
|
|
60
|
+
# GitHub.com > Settings > Developer settings > Personal access tokens > Tokens (classic)
|
|
61
|
+
# Create new token with required scopes (e.g., repo)
|
|
62
|
+
# Add to repo secrets as CLASSIC_PAT
|
|
63
|
+
|
|
64
|
+
jobs:
|
|
65
|
+
deploy:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@v4
|
|
69
|
+
with:
|
|
70
|
+
token: ${{ secrets.CLASSIC_PAT }}
|
|
71
|
+
# Classic PATs work even when org blocks fine-grained PATs
|
|
72
|
+
- language: yaml
|
|
73
|
+
label: "Use a GitHub App token (recommended)"
|
|
74
|
+
code: |
|
|
75
|
+
jobs:
|
|
76
|
+
deploy:
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
steps:
|
|
79
|
+
- name: Generate GitHub App token
|
|
80
|
+
id: app-token
|
|
81
|
+
uses: actions/create-github-app-token@v1
|
|
82
|
+
with:
|
|
83
|
+
app-id: ${{ vars.APP_ID }}
|
|
84
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
85
|
+
owner: ${{ github.repository_owner }}
|
|
86
|
+
# GitHub App tokens bypass org-level PAT restrictions entirely
|
|
87
|
+
|
|
88
|
+
- uses: actions/checkout@v4
|
|
89
|
+
with:
|
|
90
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
91
|
+
prevention:
|
|
92
|
+
- "Before using fine-grained PATs for org repositories, verify the org allows them via Settings → Third-party access → Personal access tokens."
|
|
93
|
+
- "Document the org's PAT policy in your CI/onboarding docs so contributors know which token types are accepted."
|
|
94
|
+
- "Prefer GitHub App tokens over any PAT type — they are not subject to PAT restriction policies and provide short-lived, auditable credentials."
|
|
95
|
+
- "When a PAT fails with 403, check if the error message contains 'fine-grained' before debugging scopes — the token type itself may be blocked."
|
|
96
|
+
docs:
|
|
97
|
+
- url: "https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization"
|
|
98
|
+
label: "Setting a PAT policy for your organization"
|
|
99
|
+
- url: "https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens"
|
|
100
|
+
label: "Managing personal access tokens"
|
|
101
|
+
- url: "https://stackoverflow.com/questions/79471500/github-actions-authentication-failed-for-pushing-to-repository"
|
|
102
|
+
label: "SO: GitHub Actions — Authentication Failed for Pushing to Repository (fine-grained PAT forbidden)"
|
|
103
|
+
- url: "https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/making-authenticated-api-requests-with-a-github-app-in-a-github-actions-workflow"
|
|
104
|
+
label: "Using GitHub App tokens in GitHub Actions"
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
id: permissions-auth-023
|
|
2
|
+
title: "PAT Fails with 'Not Found' on SSO-Protected Organization Repository"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- personal-access-token
|
|
7
|
+
- saml-sso
|
|
8
|
+
- organization
|
|
9
|
+
- checkout
|
|
10
|
+
- authentication
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "Not Found - https://docs\\.github\\.com/rest/repos/repos#get-a-repository"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "fatal: unable to access.*403|Syncing repository.*Not Found"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "could not read Username.*https://github\\.com"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "Not Found - https://docs.github.com/rest/repos/repos#get-a-repository"
|
|
20
|
+
- "Retrieving the default branch name"
|
|
21
|
+
- " Not Found - https://docs.github.com/rest/repos/repos#get-a-repository"
|
|
22
|
+
- "Error: Not Found - https://docs.github.com/rest/repos/repos#get-a-repository"
|
|
23
|
+
root_cause: |
|
|
24
|
+
When a GitHub organization enforces SAML SSO (Single Sign-On), every PAT
|
|
25
|
+
(Personal Access Token) used to access that organization's resources must be
|
|
26
|
+
explicitly authorized for the SSO organization — even if the token has all the
|
|
27
|
+
correct scopes (repo, read:org, etc.).
|
|
28
|
+
|
|
29
|
+
A PAT that hasn't been authorized for the SSO org will receive a 404 "Not Found"
|
|
30
|
+
response when attempting to access org resources via the API or when cloning/
|
|
31
|
+
checking out a repository. The response is 404 (not 401/403) because GitHub
|
|
32
|
+
treats an unauthorized SSO request as "resource doesn't exist for this token."
|
|
33
|
+
|
|
34
|
+
Common symptoms:
|
|
35
|
+
- `actions/checkout` fails with "Not Found" on the default branch retrieval step
|
|
36
|
+
- The PAT works locally in a browser (which has an active SSO session) but fails
|
|
37
|
+
in GitHub Actions
|
|
38
|
+
- The token has `repo` scope or `contents: read` permission but still 404s
|
|
39
|
+
|
|
40
|
+
Sources: stackoverflow.com/questions/79874764,
|
|
41
|
+
stackoverflow.com/questions/77957649,
|
|
42
|
+
GitHub Docs on SAML SSO authorization
|
|
43
|
+
fix: |
|
|
44
|
+
Authorize the PAT for the SSO organization:
|
|
45
|
+
|
|
46
|
+
1. Go to GitHub → Settings → Developer settings → Personal access tokens
|
|
47
|
+
2. Select the PAT used in the workflow
|
|
48
|
+
3. Click "Configure SSO" next to the token
|
|
49
|
+
4. Click "Authorize" next to the target organization
|
|
50
|
+
5. Complete any SSO prompts
|
|
51
|
+
|
|
52
|
+
The PAT is now authorized for that org and will work in Actions workflows.
|
|
53
|
+
|
|
54
|
+
For fine-grained PATs: ensure the token's "Resource owner" is the organization
|
|
55
|
+
(not your personal account) and that the org admin has approved it.
|
|
56
|
+
fix_code:
|
|
57
|
+
- language: yaml
|
|
58
|
+
label: "Authorize the PAT for SSO org (no workflow change needed)"
|
|
59
|
+
code: |
|
|
60
|
+
# This is a GitHub settings action, not a workflow change.
|
|
61
|
+
# GitHub.com > Settings > Developer settings > Personal access tokens
|
|
62
|
+
# > (select token) > Configure SSO > Authorize > (org name)
|
|
63
|
+
#
|
|
64
|
+
# After authorizing, your workflow using the PAT will work:
|
|
65
|
+
jobs:
|
|
66
|
+
build:
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
with:
|
|
71
|
+
repository: my-org/private-repo
|
|
72
|
+
token: ${{ secrets.MY_ORG_PAT }}
|
|
73
|
+
# MY_ORG_PAT must have 'repo' scope AND be SSO-authorized for my-org
|
|
74
|
+
- language: yaml
|
|
75
|
+
label: "Use a GitHub App token instead (recommended for SSO orgs)"
|
|
76
|
+
code: |
|
|
77
|
+
jobs:
|
|
78
|
+
build:
|
|
79
|
+
runs-on: ubuntu-latest
|
|
80
|
+
steps:
|
|
81
|
+
- name: Generate GitHub App token
|
|
82
|
+
id: app-token
|
|
83
|
+
uses: actions/create-github-app-token@v1
|
|
84
|
+
with:
|
|
85
|
+
app-id: ${{ vars.APP_ID }}
|
|
86
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
87
|
+
owner: my-org
|
|
88
|
+
# GitHub App tokens for org installations are not subject to
|
|
89
|
+
# SAML SSO PAT authorization requirements
|
|
90
|
+
- uses: actions/checkout@v4
|
|
91
|
+
with:
|
|
92
|
+
repository: my-org/private-repo
|
|
93
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
94
|
+
prevention:
|
|
95
|
+
- "After creating a new PAT for use with an SSO-enforcing organization, always authorize it via Configure SSO before adding it to Actions secrets."
|
|
96
|
+
- "Test PAT access locally with `gh auth login --with-token` and `gh repo view my-org/repo` to confirm SSO authorization before using in CI."
|
|
97
|
+
- "Prefer GitHub App tokens over PATs for organization repositories — they are not subject to SAML SSO authorization requirements."
|
|
98
|
+
- "Document which secrets require SSO authorization in your repo's CONTRIBUTING or CI documentation to prevent the same issue for new contributors."
|
|
99
|
+
docs:
|
|
100
|
+
- url: "https://docs.github.com/en/enterprise-cloud@latest/authentication/authenticating-with-saml-single-sign-on/authorizing-a-personal-access-token-for-use-with-saml-single-sign-on"
|
|
101
|
+
label: "Authorizing a PAT for use with SAML SSO"
|
|
102
|
+
- url: "https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-saml-single-sign-on-for-your-organization/about-identity-and-access-management-with-saml-single-sign-on"
|
|
103
|
+
label: "About SAML SSO for organizations"
|
|
104
|
+
- url: "https://stackoverflow.com/questions/79874764/github-actions-checkout-fails-with-not-found-error-for-sso-protected-enterprise"
|
|
105
|
+
label: "SO: checkout fails with Not Found for SSO-protected enterprise repo"
|
|
106
|
+
- url: "https://stackoverflow.com/questions/77957649/repository-not-found-error-while-using-actions-checkoutv4-to-clone-a-private-re"
|
|
107
|
+
label: "SO: Repository not found error with actions/checkout@v4 on private repo"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
id: runner-environment-055
|
|
2
|
+
title: "Android SDK Platform 37 Installed as 'android-37.0' — Builds Fail Looking for 'android-37'"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- android
|
|
7
|
+
- android-sdk
|
|
8
|
+
- gradle
|
|
9
|
+
- ubuntu-2404
|
|
10
|
+
- sdk-platform
|
|
11
|
+
- hash-string
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Failed to find target with hash string 'android-37'"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "Failed to find target.*android-37[^\\d]"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "Could not determine.*dependencies.*android-37[^\\d]"
|
|
18
|
+
flags: "i"
|
|
19
|
+
- regex: "platforms/android-37\\.0.*android-37[^\\.]"
|
|
20
|
+
flags: "i"
|
|
21
|
+
error_messages:
|
|
22
|
+
- "Failed to find target with hash string 'android-37' in: /usr/local/lib/android/sdk"
|
|
23
|
+
- "Could not determine the dependencies of task ':app:compileDebugJavaWithJavac'."
|
|
24
|
+
- "> Failed to find target with hash string 'android-37'"
|
|
25
|
+
root_cause: |
|
|
26
|
+
On the Ubuntu 24.04 runner image (from approximately image version 20260323), the
|
|
27
|
+
Android SDK Platform 37 is installed under the directory name `android-37.0` rather
|
|
28
|
+
than the expected `android-37`. Gradle and the Android Gradle Plugin (AGP) look up
|
|
29
|
+
platform targets by their hash string (`android-37`), which must exactly match the
|
|
30
|
+
subdirectory name inside `$ANDROID_SDK_ROOT/platforms/`.
|
|
31
|
+
|
|
32
|
+
When `sdkmanager` installs `platforms;android-37.0`, it creates:
|
|
33
|
+
`/usr/local/lib/android/sdk/platforms/android-37.0/`
|
|
34
|
+
|
|
35
|
+
But AGP resolves `compileSdk = 37` (or `compileSdkVersion 37`) to hash string
|
|
36
|
+
`android-37`, not `android-37.0`. The lookup fails even though the SDK is physically
|
|
37
|
+
present on the runner (runner-images#13859).
|
|
38
|
+
|
|
39
|
+
Root cause in the runner image: the `platforms;android-37.0` package name was published
|
|
40
|
+
by Google with a `.0` version suffix in the SDK package path, causing a naming
|
|
41
|
+
inconsistency between the package identifier and the installed directory name expected
|
|
42
|
+
by build tools.
|
|
43
|
+
fix: |
|
|
44
|
+
Add a workaround step that creates a symlink from `android-37` → `android-37.0` before
|
|
45
|
+
the Gradle build. This resolves the hash lookup without any source code changes.
|
|
46
|
+
|
|
47
|
+
Alternatively, pin `compileSdk` to 36 (which is installed normally) until the runner
|
|
48
|
+
image is fixed, or install `platforms;android-37` explicitly via sdkmanager — but note
|
|
49
|
+
that as of the time of writing the `android-37` (non-.0) package may not yet be
|
|
50
|
+
published to the official SDK registry.
|
|
51
|
+
fix_code:
|
|
52
|
+
- language: yaml
|
|
53
|
+
label: "Symlink workaround — link android-37 to android-37.0 before build"
|
|
54
|
+
code: |
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@v4
|
|
57
|
+
|
|
58
|
+
- name: Fix Android SDK 37 directory name mismatch
|
|
59
|
+
run: |
|
|
60
|
+
SDK_PLATFORMS="$ANDROID_SDK_ROOT/platforms"
|
|
61
|
+
if [ -d "$SDK_PLATFORMS/android-37.0" ] && [ ! -d "$SDK_PLATFORMS/android-37" ]; then
|
|
62
|
+
ln -s "$SDK_PLATFORMS/android-37.0" "$SDK_PLATFORMS/android-37"
|
|
63
|
+
echo "Symlinked android-37 -> android-37.0"
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
- name: Build APK
|
|
67
|
+
run: ./gradlew assembleDebug
|
|
68
|
+
- language: yaml
|
|
69
|
+
label: "Pin compileSdk to 36 as temporary workaround"
|
|
70
|
+
code: |
|
|
71
|
+
# In app/build.gradle or build.gradle.kts:
|
|
72
|
+
android {
|
|
73
|
+
compileSdk = 36 # Use 36 until android-37 symlink issue is resolved
|
|
74
|
+
# compileSdk = 37 # <- caused 'android-37' hash mismatch
|
|
75
|
+
}
|
|
76
|
+
- language: yaml
|
|
77
|
+
label: "Install android-37 explicitly via sdkmanager"
|
|
78
|
+
code: |
|
|
79
|
+
steps:
|
|
80
|
+
- name: Install Android SDK Platform 37
|
|
81
|
+
run: |
|
|
82
|
+
echo "y" | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager \
|
|
83
|
+
"platforms;android-37" \
|
|
84
|
+
"build-tools;37.0.0"
|
|
85
|
+
- name: Build
|
|
86
|
+
run: ./gradlew assembleDebug
|
|
87
|
+
prevention:
|
|
88
|
+
- "When upgrading `compileSdk` to a newly released Android API level, test against ubuntu-24.04 runners in a feature branch first before rolling out to all jobs."
|
|
89
|
+
- "Subscribe to runner-images releases for `ubuntu-24.04` to catch SDK directory naming changes early."
|
|
90
|
+
- "Add an explicit `sdkmanager` install step for the exact platform and build-tools version your project requires instead of relying on pre-installed SDKs."
|
|
91
|
+
docs:
|
|
92
|
+
- url: "https://github.com/actions/runner-images/issues/13859"
|
|
93
|
+
label: "runner-images#13859 — Ubuntu 24.04 installs API 37 as android-37.0; builds fail looking for android-37"
|
|
94
|
+
- url: "https://developer.android.com/studio/releases/platforms"
|
|
95
|
+
label: "Android SDK Platform releases — official platform package naming"
|
|
96
|
+
- url: "https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources"
|
|
97
|
+
label: "GitHub Docs — GitHub-hosted runner software"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
id: runner-environment-056
|
|
2
|
+
title: "Android SDK Platforms and Build Tools < v34 Removed from Runner Images (Jan 2026)"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- android
|
|
7
|
+
- android-sdk
|
|
8
|
+
- gradle
|
|
9
|
+
- runner-images
|
|
10
|
+
- ubuntu-2204
|
|
11
|
+
- macos-14
|
|
12
|
+
- windows-2022
|
|
13
|
+
- compile-sdk
|
|
14
|
+
patterns:
|
|
15
|
+
- regex: "Failed to find target with hash string 'android-3[0-3]'"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "Failed to find Build Tools revision 3[0-3]\\."
|
|
18
|
+
flags: "i"
|
|
19
|
+
- regex: "Build Tools revision.*not installed|not installed.*Build Tools"
|
|
20
|
+
flags: "i"
|
|
21
|
+
- regex: "compileSdkVersion.*3[0-3].*not found|SDK Platform.*3[0-3].*not installed"
|
|
22
|
+
flags: "i"
|
|
23
|
+
error_messages:
|
|
24
|
+
- "Failed to find target with hash string 'android-33' in: /usr/local/lib/android/sdk"
|
|
25
|
+
- "Failed to find Build Tools revision 33.0.2"
|
|
26
|
+
- "SDK Platform '33' is not installed."
|
|
27
|
+
- "Failed to find target with hash string 'android-32'"
|
|
28
|
+
root_cause: |
|
|
29
|
+
Starting January 12, 2026, GitHub removed pre-installed Android SDK platforms and build
|
|
30
|
+
tools older than version 34 from the following runner images (runner-images#13469):
|
|
31
|
+
|
|
32
|
+
- Ubuntu 22.04
|
|
33
|
+
- macOS 14 (and macOS 14 Arm64)
|
|
34
|
+
- Windows Server 2022
|
|
35
|
+
|
|
36
|
+
Affected toolchain components:
|
|
37
|
+
- SDK Platforms: android-33 and older (Android 13 / API 33, API 32, etc.)
|
|
38
|
+
- Build Tools: versions older than 34.0.0
|
|
39
|
+
|
|
40
|
+
The removal was motivated by freeing up disk space. Projects that reference
|
|
41
|
+
`compileSdkVersion 33` (or lower) in their Gradle configuration, or that install
|
|
42
|
+
specific old build-tools versions, fail immediately because the expected directories are
|
|
43
|
+
absent from `$ANDROID_SDK_ROOT/platforms/` and `$ANDROID_SDK_ROOT/build-tools/`.
|
|
44
|
+
|
|
45
|
+
Note: Ubuntu 24.04, macOS 15, and Windows Server 2025 images were not affected because
|
|
46
|
+
they never carried the older SDK platforms.
|
|
47
|
+
fix: |
|
|
48
|
+
Option A — Upgrade compileSdk and targetSdk to 34 or higher (recommended):
|
|
49
|
+
Updating to a supported API level removes the dependency on removed SDK packages.
|
|
50
|
+
API 34 (Android 14) is widely available and well-tested.
|
|
51
|
+
|
|
52
|
+
Option B — Install the required SDK platform at runtime:
|
|
53
|
+
Use `sdkmanager` in a workflow step to download and install the exact platform and
|
|
54
|
+
build tools version before the Gradle build runs. This adds ~30-60 seconds to the job.
|
|
55
|
+
fix_code:
|
|
56
|
+
- language: yaml
|
|
57
|
+
label: "Upgrade compileSdk to 35 (recommended long-term fix)"
|
|
58
|
+
code: |
|
|
59
|
+
# In app/build.gradle.kts:
|
|
60
|
+
android {
|
|
61
|
+
compileSdk = 35 # upgraded from 33 — no SDK install step needed
|
|
62
|
+
targetSdk = 35
|
|
63
|
+
minSdk = 21
|
|
64
|
+
}
|
|
65
|
+
- language: yaml
|
|
66
|
+
label: "Install removed SDK platform at runtime (keeps compileSdk 33)"
|
|
67
|
+
code: |
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
|
|
71
|
+
- name: Install Android SDK Platform 33 and Build Tools 33.0.2
|
|
72
|
+
run: |
|
|
73
|
+
echo "y" | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager \
|
|
74
|
+
"platforms;android-33" \
|
|
75
|
+
"build-tools;33.0.2"
|
|
76
|
+
|
|
77
|
+
- name: Build
|
|
78
|
+
run: ./gradlew assembleDebug
|
|
79
|
+
- language: yaml
|
|
80
|
+
label: "Install SDK using android-actions/setup-android"
|
|
81
|
+
code: |
|
|
82
|
+
steps:
|
|
83
|
+
- uses: actions/checkout@v4
|
|
84
|
+
- uses: android-actions/setup-android@v3
|
|
85
|
+
with:
|
|
86
|
+
packages: >
|
|
87
|
+
platforms;android-33
|
|
88
|
+
build-tools;33.0.2
|
|
89
|
+
- run: ./gradlew assembleRelease
|
|
90
|
+
- language: yaml
|
|
91
|
+
label: "Matrix build: validate migration to newer API level"
|
|
92
|
+
code: |
|
|
93
|
+
jobs:
|
|
94
|
+
build:
|
|
95
|
+
strategy:
|
|
96
|
+
matrix:
|
|
97
|
+
compile-sdk: [33, 34, 35]
|
|
98
|
+
runs-on: ubuntu-22.04
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/checkout@v4
|
|
101
|
+
- name: Install required SDK (only needed for API < 34)
|
|
102
|
+
if: matrix.compile-sdk < 34
|
|
103
|
+
run: |
|
|
104
|
+
echo "y" | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager \
|
|
105
|
+
"platforms;android-${{ matrix.compile-sdk }}"
|
|
106
|
+
- run: ./gradlew assembleDebug
|
|
107
|
+
env:
|
|
108
|
+
COMPILE_SDK_VERSION: ${{ matrix.compile-sdk }}
|
|
109
|
+
prevention:
|
|
110
|
+
- "Target `compileSdk` and `targetSdk` at API 34 or higher; Google Play requires API 35+ for new app submissions as of August 2025."
|
|
111
|
+
- "Audit your `build.gradle` files for hard-coded old `compileSdkVersion` values and migrate proactively during the announcement window."
|
|
112
|
+
- "Subscribe to runner-images GitHub Issues to see SDK removal announcements before they hit production workflows."
|
|
113
|
+
- "Add an explicit `sdkmanager` install step for any SDK version your build requires so the workflow is portable regardless of what's pre-installed."
|
|
114
|
+
docs:
|
|
115
|
+
- url: "https://github.com/actions/runner-images/issues/13469"
|
|
116
|
+
label: "runner-images#13469 — Android SDK platforms and build tools < v34 removed on Jan 12, 2026"
|
|
117
|
+
- url: "https://developer.android.com/tools/sdkmanager"
|
|
118
|
+
label: "Android Docs — sdkmanager command-line tool"
|
|
119
|
+
- url: "https://developer.android.com/google/play/requirements/target-sdk"
|
|
120
|
+
label: "Google Play — target API level requirements"
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
id: runner-environment-052
|
|
2
|
+
title: "Docker Multi-Platform Build Uses Matrix per Platform — Tags Overwrite Each Other"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- docker
|
|
7
|
+
- buildx
|
|
8
|
+
- multi-platform
|
|
9
|
+
- matrix
|
|
10
|
+
- ghcr
|
|
11
|
+
- manifest
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "no matching manifest for .* in the manifest list entries"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "image operating system .* does not match host operating system"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "manifest unknown"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "no matching manifest for linux/amd64 in the manifest list entries"
|
|
21
|
+
- "Error response from daemon: manifest unknown: manifest unknown"
|
|
22
|
+
- "image operating system \"linux\" does not match host operating system \"linux\""
|
|
23
|
+
root_cause: |
|
|
24
|
+
A common pattern for multi-platform Docker images in GitHub Actions is to use
|
|
25
|
+
`strategy.matrix` with one platform per job and `docker/build-push-action` in
|
|
26
|
+
each job. This approach is incorrect: each job pushes a single-platform image
|
|
27
|
+
to the same tag, and each push *overwrites* the previous one. There is no merging
|
|
28
|
+
of single-platform images into a multi-platform manifest list.
|
|
29
|
+
|
|
30
|
+
After all matrix jobs complete, the tag only contains the image from the last
|
|
31
|
+
job to push — typically whichever platform finished last. All other platforms are
|
|
32
|
+
silently lost.
|
|
33
|
+
|
|
34
|
+
When another host tries to pull the image on a different architecture (e.g.,
|
|
35
|
+
linux/arm64 pulling after linux/amd64 was last to write), they receive:
|
|
36
|
+
"no matching manifest for linux/arm64 in the manifest list entries"
|
|
37
|
+
|
|
38
|
+
The workflow itself reports success — all matrix jobs push successfully. The
|
|
39
|
+
failure is silent until image consumers on other architectures try to pull.
|
|
40
|
+
|
|
41
|
+
Sources: stackoverflow.com/questions/79315376,
|
|
42
|
+
Docker multi-platform build documentation
|
|
43
|
+
fix: |
|
|
44
|
+
Use a single `docker/build-push-action` step with comma-separated platforms
|
|
45
|
+
instead of a matrix. Docker Buildx handles building all platforms in one job
|
|
46
|
+
and pushes a proper multi-platform manifest list.
|
|
47
|
+
|
|
48
|
+
If you need dedicated builders for each platform (e.g., native ARM runners),
|
|
49
|
+
use the "bake + merge manifest" pattern with `docker buildx imagetools create`
|
|
50
|
+
to merge per-platform digests into a single manifest after all builds complete.
|
|
51
|
+
fix_code:
|
|
52
|
+
- language: yaml
|
|
53
|
+
label: "Single buildx job with comma-separated platforms (recommended)"
|
|
54
|
+
code: |
|
|
55
|
+
jobs:
|
|
56
|
+
build-and-push:
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
permissions:
|
|
59
|
+
packages: write
|
|
60
|
+
contents: read
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@v4
|
|
63
|
+
|
|
64
|
+
- name: Set up QEMU
|
|
65
|
+
uses: docker/setup-qemu-action@v3
|
|
66
|
+
|
|
67
|
+
- name: Set up Docker Buildx
|
|
68
|
+
uses: docker/setup-buildx-action@v3
|
|
69
|
+
|
|
70
|
+
- name: Login to GHCR
|
|
71
|
+
uses: docker/login-action@v3
|
|
72
|
+
with:
|
|
73
|
+
registry: ghcr.io
|
|
74
|
+
username: ${{ github.actor }}
|
|
75
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
76
|
+
|
|
77
|
+
- name: Build and push multi-platform image
|
|
78
|
+
uses: docker/build-push-action@v6
|
|
79
|
+
with:
|
|
80
|
+
push: true
|
|
81
|
+
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
|
82
|
+
# All platforms in one step → proper manifest list created
|
|
83
|
+
tags: ghcr.io/${{ github.repository_owner }}/myapp:latest
|
|
84
|
+
- language: yaml
|
|
85
|
+
label: "Native-builder pattern: build per platform, merge manifest (advanced)"
|
|
86
|
+
code: |
|
|
87
|
+
# Build per-platform in parallel jobs, then merge into a manifest list
|
|
88
|
+
jobs:
|
|
89
|
+
build:
|
|
90
|
+
strategy:
|
|
91
|
+
matrix:
|
|
92
|
+
include:
|
|
93
|
+
- platform: linux/amd64
|
|
94
|
+
runner: ubuntu-latest
|
|
95
|
+
- platform: linux/arm64
|
|
96
|
+
runner: ubuntu-24.04-arm # native ARM runner
|
|
97
|
+
runs-on: ${{ matrix.runner }}
|
|
98
|
+
outputs:
|
|
99
|
+
digest: ${{ steps.push.outputs.digest }}
|
|
100
|
+
steps:
|
|
101
|
+
- uses: actions/checkout@v4
|
|
102
|
+
- uses: docker/setup-buildx-action@v3
|
|
103
|
+
- uses: docker/login-action@v3
|
|
104
|
+
with:
|
|
105
|
+
registry: ghcr.io
|
|
106
|
+
username: ${{ github.actor }}
|
|
107
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
108
|
+
- name: Build and push by digest (no tag yet)
|
|
109
|
+
id: push
|
|
110
|
+
uses: docker/build-push-action@v6
|
|
111
|
+
with:
|
|
112
|
+
push: true
|
|
113
|
+
platforms: ${{ matrix.platform }}
|
|
114
|
+
# Push by digest only — do NOT set a tag here
|
|
115
|
+
outputs: type=image,name=ghcr.io/${{ github.repository_owner }}/myapp,push-by-digest=true,name-canonical=true
|
|
116
|
+
|
|
117
|
+
merge:
|
|
118
|
+
needs: build
|
|
119
|
+
runs-on: ubuntu-latest
|
|
120
|
+
steps:
|
|
121
|
+
- uses: docker/login-action@v3
|
|
122
|
+
with:
|
|
123
|
+
registry: ghcr.io
|
|
124
|
+
username: ${{ github.actor }}
|
|
125
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
126
|
+
- name: Create and push multi-platform manifest
|
|
127
|
+
run: |
|
|
128
|
+
docker buildx imagetools create \
|
|
129
|
+
-t ghcr.io/${{ github.repository_owner }}/myapp:latest \
|
|
130
|
+
${{ needs.build.outputs.digest }}
|
|
131
|
+
# Merges per-platform digests into one manifest list
|
|
132
|
+
prevention:
|
|
133
|
+
- "Never use strategy.matrix to build separate platforms and push to the same tag — each push overwrites the previous."
|
|
134
|
+
- "Use a single build-push-action step with comma-separated platforms for most multi-platform use cases."
|
|
135
|
+
- "Validate multi-platform images after push with `docker buildx imagetools inspect ghcr.io/owner/image:tag` to confirm all platforms are present."
|
|
136
|
+
- "When matrix builds are needed for native performance, use push-by-digest and a fan-in merge job."
|
|
137
|
+
docs:
|
|
138
|
+
- url: "https://docs.docker.com/build/ci/github-actions/multi-platform/"
|
|
139
|
+
label: "Docker: Multi-platform image with GitHub Actions"
|
|
140
|
+
- url: "https://github.com/docker/build-push-action"
|
|
141
|
+
label: "docker/build-push-action — multi-platform examples"
|
|
142
|
+
- url: "https://stackoverflow.com/questions/79315376/docker-multiplatform-image-pushed-successfully-to-ghcr-but-pulling-image-result"
|
|
143
|
+
label: "SO: Docker multiplatform image pushed successfully but pulling results in manifest not found"
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
id: runner-environment-053
|
|
2
|
+
title: "Node 24 Action Fails on Self-Hosted Runner — 'using: node24 is not supported'"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- node24
|
|
7
|
+
- self-hosted-runner
|
|
8
|
+
- runner-version
|
|
9
|
+
- node-runtime
|
|
10
|
+
- actions-checkout
|
|
11
|
+
- actions-cache
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "'using: node24' is not supported"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "Parameter.*using.*node24.*is not supported"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "node24.*not supported.*use.*node12.*node16.*node20"
|
|
18
|
+
flags: "i"
|
|
19
|
+
- regex: "This version of the Actions runner is too old to run node24"
|
|
20
|
+
flags: "i"
|
|
21
|
+
error_messages:
|
|
22
|
+
- "Specified argument was out of the range of valid values. (Parameter ''using: node24' is not supported, use 'docker', 'node12', 'node16' or 'node20' instead.')"
|
|
23
|
+
- "'using: node24' is not supported, use 'docker', 'node12', 'node16' or 'node20' instead."
|
|
24
|
+
root_cause: |
|
|
25
|
+
GitHub Actions runner v2.327.1 (released July 25, 2025) was the first version to add
|
|
26
|
+
support for the `node24` runtime. Actions that bumped their major version to run on
|
|
27
|
+
Node.js 24 (including `actions/checkout@v5`, `actions/cache@v5`,
|
|
28
|
+
`actions/upload-artifact@v6`, and `actions/setup-node@v5`) specify `using: node24` in
|
|
29
|
+
their `action.yml`. When one of these actions runs on a self-hosted runner older than
|
|
30
|
+
v2.327.1, the runner's `ActionManifestManager` throws a `System.ArgumentOutOfRangeException`
|
|
31
|
+
because it does not recognise `node24` as a valid runtime identifier — it only knows
|
|
32
|
+
`docker`, `node12`, `node16`, and `node20`.
|
|
33
|
+
|
|
34
|
+
Common scenarios:
|
|
35
|
+
- Bumping a pinned `actions/checkout@v4` → `actions/checkout@v5` without first updating
|
|
36
|
+
the self-hosted runner.
|
|
37
|
+
- Relying on automatic runner self-update: runners sometimes auto-update the agent binary
|
|
38
|
+
but the worker process still runs under the old version, causing a mismatch
|
|
39
|
+
(actions/runner#4064).
|
|
40
|
+
- Using Dependabot or Renovate to auto-bump action versions across a repo.
|
|
41
|
+
fix: |
|
|
42
|
+
Update all self-hosted runners to version 2.327.1 or later before upgrading any action
|
|
43
|
+
to a version that requires `node24`. Steps:
|
|
44
|
+
|
|
45
|
+
1. Download and install the latest runner from:
|
|
46
|
+
https://github.com/actions/runner/releases/latest
|
|
47
|
+
2. Verify the version with: `./run.sh --version` (Linux/macOS) or `.\run.cmd --version`
|
|
48
|
+
(Windows).
|
|
49
|
+
3. If automatic self-update is enabled but stuck, stop the runner service, delete the
|
|
50
|
+
cached `_work/_update` directory, and restart — or manually install the new version.
|
|
51
|
+
4. For containerised runners (ARC, summerwind/actions-runner-dind), update the base image
|
|
52
|
+
tag to a version that ships runner ≥ 2.327.1.
|
|
53
|
+
|
|
54
|
+
If you cannot update the runner immediately, pin the actions back to the last v4/v5
|
|
55
|
+
compatible release and schedule the runner upgrade.
|
|
56
|
+
fix_code:
|
|
57
|
+
- language: yaml
|
|
58
|
+
label: "Pin to node24-compatible action versions — requires runner ≥ 2.327.1"
|
|
59
|
+
code: |
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/checkout@v5 # node24 — runner must be ≥ 2.327.1
|
|
62
|
+
- uses: actions/cache@v5 # node24 — runner must be ≥ 2.327.1
|
|
63
|
+
- uses: actions/upload-artifact@v6 # node24 — runner must be ≥ 2.327.1
|
|
64
|
+
- uses: actions/setup-node@v5 # node24 — runner must be ≥ 2.327.1
|
|
65
|
+
- language: yaml
|
|
66
|
+
label: "Temporary pin to node20 versions while runner is being updated"
|
|
67
|
+
code: |
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4 # still node20, compatible with older runners
|
|
70
|
+
- uses: actions/cache@v4 # still node20
|
|
71
|
+
- uses: actions/upload-artifact@v4 # still node20
|
|
72
|
+
- uses: actions/setup-node@v4 # still node20
|
|
73
|
+
- language: yaml
|
|
74
|
+
label: "Verify runner version in workflow (diagnostic)"
|
|
75
|
+
code: |
|
|
76
|
+
steps:
|
|
77
|
+
- name: Print runner version
|
|
78
|
+
run: echo "Runner version ${{ runner.version }}"
|
|
79
|
+
# runner.version must be >= 2.327.1 for node24 actions
|
|
80
|
+
prevention:
|
|
81
|
+
- "Before bumping any action to a node24 major version, check all self-hosted runner versions and update them first."
|
|
82
|
+
- "Add a minimum runner version check step or GitHub Actions required runner version policy for your organisation."
|
|
83
|
+
- "Subscribe to runner-images and actions/runner release announcements to learn about node runtime changes in advance."
|
|
84
|
+
- "For Dependabot/Renovate auto-bumps, add a CI gate that blocks merging action version upgrades until runner compatibility is confirmed."
|
|
85
|
+
docs:
|
|
86
|
+
- url: "https://github.com/actions/checkout/issues/2240"
|
|
87
|
+
label: "actions/checkout#2240 — Breaking change with v5: 'using: node24 is not supported' (27 reactions)"
|
|
88
|
+
- url: "https://github.com/actions/runner/releases/tag/v2.327.1"
|
|
89
|
+
label: "actions/runner v2.327.1 — First release with Node.js 24 support"
|
|
90
|
+
- url: "https://github.com/actions/runner/issues/4064"
|
|
91
|
+
label: "actions/runner#4064 — Auto-update doesn't take node20 → node24 into account, cascading failures"
|
|
92
|
+
- url: "https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/"
|
|
93
|
+
label: "GitHub Changelog — Deprecation of Node 20 on GitHub Actions runners"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
id: runner-environment-054
|
|
2
|
+
title: "Python 3.9 Removed from Runner Images — Version Not Found in Local Cache"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- python
|
|
7
|
+
- python-39
|
|
8
|
+
- runner-images
|
|
9
|
+
- toolcache
|
|
10
|
+
- setup-python
|
|
11
|
+
- eol
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Version '3\\.9[^']*' was not found in the local cache"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "python.*3\\.9.*not found.*cache|not found.*python.*3\\.9"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "Unable to find python version.*3\\.9"
|
|
18
|
+
flags: "i"
|
|
19
|
+
- regex: "Couldn't find a version that satisfies.*python.*3\\.9"
|
|
20
|
+
flags: "i"
|
|
21
|
+
error_messages:
|
|
22
|
+
- "Version '3.9.x' was not found in the local cache"
|
|
23
|
+
- "Unable to find the requested Python version (3.9.x) in the local tool cache"
|
|
24
|
+
- "Error: Version 3.9 with arch x64 not found"
|
|
25
|
+
root_cause: |
|
|
26
|
+
Python 3.9 reached end-of-life on October 5, 2025. Starting January 12, 2026, GitHub
|
|
27
|
+
removed the pre-cached Python 3.9 toolchain from all runner images (ubuntu-22.04,
|
|
28
|
+
ubuntu-24.04, macOS-14, macOS-15, Windows Server 2022/2025). Additionally, on
|
|
29
|
+
Windows-based images Python 3.12 replaced 3.9 as the default version
|
|
30
|
+
(runner-images#13468).
|
|
31
|
+
|
|
32
|
+
Workflows are affected when:
|
|
33
|
+
1. `actions/setup-python` is used with `python-version: '3.9'` without a fallback —
|
|
34
|
+
the action tries the local toolcache first and fails when the pre-cached entry is gone.
|
|
35
|
+
2. A workflow assumes the system `python` or `python3` binary is 3.9 on Windows
|
|
36
|
+
(where it was the previous default).
|
|
37
|
+
3. A workflow calls `python3.9` directly (e.g. `python3.9 manage.py …`) without
|
|
38
|
+
installing it first.
|
|
39
|
+
fix: |
|
|
40
|
+
Option A — Install Python 3.9 at runtime via actions/setup-python (recommended):
|
|
41
|
+
Add `actions/setup-python` with a specific version. The action will download and
|
|
42
|
+
install Python 3.9 from the GitHub-managed toolcache CDN even though it is no longer
|
|
43
|
+
pre-cached on the image.
|
|
44
|
+
|
|
45
|
+
Option B — Migrate to a supported Python version:
|
|
46
|
+
Python 3.9 is EOL. Migrate to Python 3.11, 3.12, or 3.13 to benefit from active
|
|
47
|
+
security patches. Test your dependency set against the newer version.
|
|
48
|
+
|
|
49
|
+
Option C — Pin a newer default for Windows:
|
|
50
|
+
If you relied on the system default `python` being 3.9 on Windows, add an explicit
|
|
51
|
+
`actions/setup-python` step to pin the exact version you need.
|
|
52
|
+
fix_code:
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: "Install Python 3.9 at runtime (works despite toolcache removal)"
|
|
55
|
+
code: |
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@v4
|
|
58
|
+
- uses: actions/setup-python@v5
|
|
59
|
+
with:
|
|
60
|
+
python-version: '3.9' # downloaded from CDN — no longer pre-cached
|
|
61
|
+
- run: python --version
|
|
62
|
+
- language: yaml
|
|
63
|
+
label: "Migrate to Python 3.12 (recommended — actively supported)"
|
|
64
|
+
code: |
|
|
65
|
+
steps:
|
|
66
|
+
- uses: actions/checkout@v4
|
|
67
|
+
- uses: actions/setup-python@v5
|
|
68
|
+
with:
|
|
69
|
+
python-version: '3.12'
|
|
70
|
+
- run: pip install -r requirements.txt
|
|
71
|
+
- language: yaml
|
|
72
|
+
label: "Matrix across multiple Python versions"
|
|
73
|
+
code: |
|
|
74
|
+
jobs:
|
|
75
|
+
test:
|
|
76
|
+
strategy:
|
|
77
|
+
matrix:
|
|
78
|
+
python-version: ['3.11', '3.12', '3.13']
|
|
79
|
+
runs-on: ubuntu-latest
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/checkout@v4
|
|
82
|
+
- uses: actions/setup-python@v5
|
|
83
|
+
with:
|
|
84
|
+
python-version: ${{ matrix.python-version }}
|
|
85
|
+
- run: pip install -r requirements.txt && pytest
|
|
86
|
+
prevention:
|
|
87
|
+
- "Always use `actions/setup-python` with an explicit `python-version` — never rely on the system pre-cached version."
|
|
88
|
+
- "Subscribe to runner-images EOL announcements to plan Python version migrations before removal dates."
|
|
89
|
+
- "Add Python version to `.python-version` or `pyproject.toml` and let `actions/setup-python` read it via `python-version-file`."
|
|
90
|
+
- "Avoid hard-coding EOL Python versions; use `~3.12` or `>=3.11` range specifiers where the toolchain supports them."
|
|
91
|
+
docs:
|
|
92
|
+
- url: "https://github.com/actions/runner-images/issues/13468"
|
|
93
|
+
label: "runner-images#13468 — Python 3.9 removed; Python 3.12 becomes default on Windows (Jan 12, 2026)"
|
|
94
|
+
- url: "https://github.com/actions/setup-python"
|
|
95
|
+
label: "actions/setup-python — installs any Python version at runtime from CDN"
|
|
96
|
+
- url: "https://devguide.python.org/versions/"
|
|
97
|
+
label: "Python Developer's Guide — supported versions and EOL dates"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
id: silent-failures-026
|
|
2
|
+
title: "Docker Run Exit Code Masked When Output Is Piped to tee"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- docker
|
|
7
|
+
- exit-code
|
|
8
|
+
- pipe
|
|
9
|
+
- tee
|
|
10
|
+
- bash
|
|
11
|
+
- pipefail
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "docker run.*\\|.*tee"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "PIPESTATUS|pipefail"
|
|
16
|
+
flags: "i"
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Pipe status: 0"
|
|
19
|
+
- "Exit status: 0"
|
|
20
|
+
- "The command succeeded."
|
|
21
|
+
root_cause: |
|
|
22
|
+
When a `docker run` command is piped to `tee` for dual-output (file + console),
|
|
23
|
+
the shell's `$?` variable captures the exit code of the *last command in the
|
|
24
|
+
pipeline* — which is `tee`. Since `tee` always exits 0 (unless it cannot write
|
|
25
|
+
to the output file), the workflow step always reports success even when the
|
|
26
|
+
container process exits with a non-zero code.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
docker run myimage pytest tests/ 2>&1 | tee output.txt
|
|
30
|
+
echo $? # Always 0 — tee's exit code, not docker's
|
|
31
|
+
|
|
32
|
+
GitHub Actions enables `set -e` and `set -o pipefail` by default for bash steps,
|
|
33
|
+
but `pipefail` only propagates failures if the pipeline command itself fails.
|
|
34
|
+
When the output of `docker run` is piped, the runner still sees `tee`'s exit code
|
|
35
|
+
as the step's result.
|
|
36
|
+
|
|
37
|
+
`${PIPESTATUS[0]}` is the bash array of exit codes for each pipeline segment, but
|
|
38
|
+
it must be captured *immediately* after the pipeline — it is overwritten by the
|
|
39
|
+
next command.
|
|
40
|
+
|
|
41
|
+
Sources: stackoverflow.com/questions/79075923
|
|
42
|
+
fix: |
|
|
43
|
+
Option 1 (Recommended): Capture the docker exit code via PIPESTATUS immediately
|
|
44
|
+
after the pipeline, before running any other command.
|
|
45
|
+
|
|
46
|
+
Option 2: Run docker without piping, capturing output to a variable first, then
|
|
47
|
+
echo it and write to file. This avoids the PIPESTATUS problem entirely.
|
|
48
|
+
|
|
49
|
+
Option 3: Enable `set -o pipefail` in the step shell (note: GitHub Actions bash
|
|
50
|
+
steps do NOT enable pipefail by default despite documentation implying otherwise —
|
|
51
|
+
test explicitly).
|
|
52
|
+
fix_code:
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: "Capture docker exit code via PIPESTATUS[0]"
|
|
55
|
+
code: |
|
|
56
|
+
- name: Run tests in Docker
|
|
57
|
+
run: |
|
|
58
|
+
docker run myimage pytest tests/ 2>&1 | tee output.txt
|
|
59
|
+
DOCKER_EXIT=${PIPESTATUS[0]}
|
|
60
|
+
# PIPESTATUS must be captured immediately after the pipeline
|
|
61
|
+
echo "Docker exit code: $DOCKER_EXIT"
|
|
62
|
+
if [ "$DOCKER_EXIT" -ne 0 ]; then
|
|
63
|
+
echo "Tests failed inside container."
|
|
64
|
+
exit "$DOCKER_EXIT"
|
|
65
|
+
fi
|
|
66
|
+
- language: yaml
|
|
67
|
+
label: "Avoid piping: capture output in variable, then write and echo"
|
|
68
|
+
code: |
|
|
69
|
+
- name: Run tests in Docker
|
|
70
|
+
run: |
|
|
71
|
+
# Capture output without piping — preserves $? correctly
|
|
72
|
+
docker_output=$(docker run myimage pytest tests/ 2>&1)
|
|
73
|
+
DOCKER_EXIT=$?
|
|
74
|
+
echo "$docker_output" # Show in Actions log
|
|
75
|
+
echo "$docker_output" > output.txt # Also write to file
|
|
76
|
+
if [ "$DOCKER_EXIT" -ne 0 ]; then
|
|
77
|
+
echo "Tests failed (exit $DOCKER_EXIT)"
|
|
78
|
+
exit "$DOCKER_EXIT"
|
|
79
|
+
fi
|
|
80
|
+
- language: yaml
|
|
81
|
+
label: "Use process substitution to write to file without a pipeline"
|
|
82
|
+
code: |
|
|
83
|
+
- name: Run tests in Docker
|
|
84
|
+
run: |
|
|
85
|
+
# tee via process substitution — preserves docker's exit code
|
|
86
|
+
docker run myimage pytest tests/ 2>&1 > >(tee output.txt)
|
|
87
|
+
# $? is now docker's exit code, not tee's
|
|
88
|
+
prevention:
|
|
89
|
+
- "Never check $? after a piped command to determine success — always use ${PIPESTATUS[0]} or avoid piping."
|
|
90
|
+
- "Add an explicit exit-code check after any docker run step, especially when the step uses | tee or redirections."
|
|
91
|
+
- "Consider adding `set -o pipefail` at the top of multi-command run steps to catch pipeline failures automatically."
|
|
92
|
+
- "Test workflows with a deliberately failing container command to confirm the step actually fails as expected."
|
|
93
|
+
docs:
|
|
94
|
+
- url: "https://www.gnu.org/software/bash/manual/bash.html#index-PIPESTATUS"
|
|
95
|
+
label: "Bash manual — PIPESTATUS"
|
|
96
|
+
- url: "https://stackoverflow.com/questions/79075923/how-can-i-get-the-error-codes-in-github-actions-shell-when-a-docker-run-command"
|
|
97
|
+
label: "SO: Get error codes in github-actions shell when docker run command fails while piping output"
|
|
98
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions"
|
|
99
|
+
label: "GitHub Actions workflow commands"
|
package/package.json
CHANGED