@htekdev/actions-debugger 1.0.18 → 1.0.20
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/create-github-app-token-jwt-decode-error.yml +79 -0
- package/errors/permissions-auth/create-github-app-token-workflows-permission-denied.yml +84 -0
- 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/docker-buildx-matrix-platform-tag-overwrite.yml +143 -0
- package/errors/silent-failures/attest-build-provenance-push-to-registry-404.yml +105 -0
- package/errors/silent-failures/docker-run-pipe-tee-exit-code-zero.yml +99 -0
- package/errors/triggers/workflow-run-artifact-download-missing-run-id.yml +106 -0
- package/errors/yaml-syntax/reusable-workflow-env-context-with-inputs.yml +102 -0
- package/package.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
id: permissions-auth-021
|
|
2
|
+
title: "create-github-app-token: A JSON web token could not be decoded (PEM key format)"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- github-app
|
|
7
|
+
- jwt
|
|
8
|
+
- private-key
|
|
9
|
+
- pem
|
|
10
|
+
- create-github-app-token
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "A JSON web token could not be decoded"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "Failed to create token for .+\\(attempt \\d+\\): A JSON web token could not be decoded"
|
|
15
|
+
flags: "i"
|
|
16
|
+
error_messages:
|
|
17
|
+
- "A JSON web token could not be decoded - https://docs.github.com/rest"
|
|
18
|
+
- "Failed to create token for \"repo-name\" (attempt 1): A JSON web token could not be decoded"
|
|
19
|
+
- "RequestError [HttpError]: A JSON web token could not be decoded"
|
|
20
|
+
root_cause: |
|
|
21
|
+
The `actions/create-github-app-token` action signs a JWT using the GitHub App private key.
|
|
22
|
+
When the private key stored in the repository secret is malformed, every attempt to generate
|
|
23
|
+
a token fails with a 401 and this message. The action retries 4 times then fails the step.
|
|
24
|
+
|
|
25
|
+
Common causes:
|
|
26
|
+
1. Trailing whitespace or the final newline stripped when pasting the PEM into the GitHub
|
|
27
|
+
Secrets UI (the textarea strips trailing whitespace on save)
|
|
28
|
+
2. Windows-style CRLF line endings introduced during copy-paste from a text editor
|
|
29
|
+
3. Missing PEM header (`-----BEGIN RSA PRIVATE KEY-----`) or footer line
|
|
30
|
+
4. The Base64 body is correct but line breaks inside the key were removed
|
|
31
|
+
5. The full `.pem` file was Base64-encoded before being stored (double-encoded)
|
|
32
|
+
|
|
33
|
+
The GitHub API returns HTTP 401 with the message "A JSON web token could not be decoded"
|
|
34
|
+
whenever the JWT signature cannot be verified, which always indicates a malformed key.
|
|
35
|
+
fix: |
|
|
36
|
+
Store the private key exactly as downloaded from GitHub — raw PEM format with LF line
|
|
37
|
+
endings, including the header and footer. Use the GitHub CLI to set the secret from the
|
|
38
|
+
downloaded file rather than copy-pasting it through the UI.
|
|
39
|
+
|
|
40
|
+
1. Download the private key from the GitHub App settings page
|
|
41
|
+
(Settings → Developer settings → GitHub Apps → Your App → Private keys)
|
|
42
|
+
2. Set the secret from the file:
|
|
43
|
+
gh secret set APP_PRIVATE_KEY < my-app.private-key.pem
|
|
44
|
+
3. Verify the secret was stored correctly by checking the Actions secrets list shows
|
|
45
|
+
the key was updated recently
|
|
46
|
+
|
|
47
|
+
If the key is already in a secret and you cannot change it, create a new private key
|
|
48
|
+
from the App settings and re-set the secret from the fresh download.
|
|
49
|
+
fix_code:
|
|
50
|
+
- language: shell
|
|
51
|
+
label: "Set secret directly from downloaded .pem file (preserves newlines)"
|
|
52
|
+
code: |
|
|
53
|
+
# Download the .pem from GitHub App settings, then:
|
|
54
|
+
gh secret set APP_PRIVATE_KEY < my-app.2024-01-15.private-key.pem
|
|
55
|
+
- language: yaml
|
|
56
|
+
label: "Correct workflow: reference private key secret in action"
|
|
57
|
+
code: |
|
|
58
|
+
- uses: actions/create-github-app-token@v1
|
|
59
|
+
id: app-token
|
|
60
|
+
with:
|
|
61
|
+
app-id: ${{ vars.APP_ID }}
|
|
62
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
63
|
+
|
|
64
|
+
- name: Use generated token
|
|
65
|
+
run: echo "Token generated successfully"
|
|
66
|
+
env:
|
|
67
|
+
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
68
|
+
prevention:
|
|
69
|
+
- "Always set the APP_PRIVATE_KEY secret using `gh secret set KEY < file.pem`, never by copy-pasting through the browser UI"
|
|
70
|
+
- "Regenerate and re-set the private key if you suspect formatting was corrupted during initial setup"
|
|
71
|
+
- "Verify the secret value in the GitHub UI shows the correct 'Updated' timestamp after setting via CLI"
|
|
72
|
+
- "Store private keys in a secrets manager (e.g., HashiCorp Vault, AWS Secrets Manager) and fetch at runtime to avoid manual copy-paste errors"
|
|
73
|
+
docs:
|
|
74
|
+
- url: "https://github.com/actions/create-github-app-token"
|
|
75
|
+
label: "actions/create-github-app-token README"
|
|
76
|
+
- url: "https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-private-key-for-a-github-app"
|
|
77
|
+
label: "Generating a private key for a GitHub App"
|
|
78
|
+
- url: "https://github.com/actions/create-github-app-token/issues/153"
|
|
79
|
+
label: "actions/create-github-app-token#153: JWT decode error (27 comments)"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
id: permissions-auth-022
|
|
2
|
+
title: "create-github-app-token: push rejected — GitHub App missing `workflows` permission"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- github-app
|
|
7
|
+
- create-github-app-token
|
|
8
|
+
- workflows-permission
|
|
9
|
+
- push
|
|
10
|
+
- git-push
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "refusing to allow a GitHub App to create or update workflow .+ without `workflows` permission"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "remote rejected.*refusing to allow a GitHub App.*workflows.*permission"
|
|
15
|
+
flags: "i"
|
|
16
|
+
error_messages:
|
|
17
|
+
- "! [remote rejected] -> (refusing to allow a GitHub App to create or update workflow `.github/workflows/` without `workflows` permission)"
|
|
18
|
+
- "refusing to allow a GitHub App to create or update workflow `.github/workflows/my-workflow.yml` without `workflows` permission"
|
|
19
|
+
- "error: failed to push some refs to ''"
|
|
20
|
+
root_cause: |
|
|
21
|
+
When a workflow uses `actions/create-github-app-token` to generate a token and then uses
|
|
22
|
+
that token to push commits that include changes to `.github/workflows/` files, GitHub
|
|
23
|
+
enforces that the GitHub App's installation token has the `workflows` write permission.
|
|
24
|
+
|
|
25
|
+
This permission gate was tightened in `actions/create-github-app-token` v2.1.4 due to
|
|
26
|
+
changes in `octokit/auth-app.js` (PR #712). Tokens generated without explicitly requesting
|
|
27
|
+
the `workflows` write permission no longer inherit it automatically, even if the GitHub App
|
|
28
|
+
itself has the permission enabled in its settings.
|
|
29
|
+
|
|
30
|
+
Two things must be true for the push to succeed:
|
|
31
|
+
1. The GitHub App has the `Workflows` repository permission set to Read & Write in the
|
|
32
|
+
App's settings page
|
|
33
|
+
2. The `permission-workflows: write` input is passed to `actions/create-github-app-token`
|
|
34
|
+
so the generated token explicitly includes that permission
|
|
35
|
+
fix: |
|
|
36
|
+
Add `permission-workflows: write` to the `actions/create-github-app-token` step. Also
|
|
37
|
+
verify that the GitHub App itself has the Workflows permission enabled in its settings.
|
|
38
|
+
|
|
39
|
+
Steps:
|
|
40
|
+
1. Go to https://github.com/settings/apps/YOUR_APP/permissions
|
|
41
|
+
2. Under "Repository permissions", set "Workflows" to "Read and write"
|
|
42
|
+
3. Save changes and accept the permission update for affected organizations/users
|
|
43
|
+
4. Update your workflow to pass `permission-workflows: write`
|
|
44
|
+
fix_code:
|
|
45
|
+
- language: yaml
|
|
46
|
+
label: "Add workflows write permission to token generation step"
|
|
47
|
+
code: |
|
|
48
|
+
- uses: actions/create-github-app-token@v1
|
|
49
|
+
id: app-token
|
|
50
|
+
with:
|
|
51
|
+
app-id: ${{ vars.APP_ID }}
|
|
52
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
53
|
+
permission-workflows: write # ✅ Required when pushing workflow file changes
|
|
54
|
+
|
|
55
|
+
- name: Configure git with app token
|
|
56
|
+
run: |
|
|
57
|
+
git config user.name "github-actions[bot]"
|
|
58
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
59
|
+
git remote set-url origin https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository }}
|
|
60
|
+
|
|
61
|
+
- name: Push changes including workflow files
|
|
62
|
+
run: git push
|
|
63
|
+
- language: yaml
|
|
64
|
+
label: "Fallback: pin to v2.1.3 if immediate permission change is not possible"
|
|
65
|
+
code: |
|
|
66
|
+
# Pinning to a pre-v2.1.4 version is a temporary workaround only.
|
|
67
|
+
# Migrate to permission-workflows: write as soon as possible.
|
|
68
|
+
- uses: actions/create-github-app-token@v2.1.3
|
|
69
|
+
id: app-token
|
|
70
|
+
with:
|
|
71
|
+
app-id: ${{ vars.APP_ID }}
|
|
72
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
73
|
+
prevention:
|
|
74
|
+
- "Always specify `permission-workflows: write` when the token will be used to push `.github/workflows/` changes"
|
|
75
|
+
- "Verify the GitHub App's Workflows repository permission is set to Read & Write in the App settings"
|
|
76
|
+
- "Avoid pinning to old versions as a workaround — explicitly grant the required permission instead"
|
|
77
|
+
- "Use separate token generation steps with minimal permissions for each distinct operation"
|
|
78
|
+
docs:
|
|
79
|
+
- url: "https://github.com/actions/create-github-app-token"
|
|
80
|
+
label: "actions/create-github-app-token README"
|
|
81
|
+
- url: "https://docs.github.com/en/rest/authentication/permissions-required-for-github-apps?apiVersion=2022-11-28#repository-permissions-for-workflows"
|
|
82
|
+
label: "Permissions required for GitHub Apps — Workflows"
|
|
83
|
+
- url: "https://github.com/actions/create-github-app-token/issues/301"
|
|
84
|
+
label: "actions/create-github-app-token#301: workflows permission push rejection"
|
|
@@ -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,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,105 @@
|
|
|
1
|
+
id: silent-failures-025
|
|
2
|
+
title: "attest-build-provenance: OCIError 404 when image not pushed to registry before attestation"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- attest-build-provenance
|
|
7
|
+
- oci
|
|
8
|
+
- container-registry
|
|
9
|
+
- slsa
|
|
10
|
+
- attestation
|
|
11
|
+
- push-to-registry
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "OCIError: Error uploading artifact to container registry"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "Error fetching .+/manifests/sha256:[a-f0-9]+ - expected 200, received 404"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "expected 200, received 404"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Error: OCIError: Error uploading artifact to container registry"
|
|
21
|
+
- "Error: Error fetching https://ghcr.io/v2/owner/repo/manifests/sha256:abc123... - expected 200, received 404"
|
|
22
|
+
root_cause: |
|
|
23
|
+
`actions/attest-build-provenance` with `push-to-registry: true` fetches the image manifest
|
|
24
|
+
from the container registry to embed it in the attestation bundle. If the image was built
|
|
25
|
+
with `load: true` in `docker/build-push-action` but `push: false` (or a conditional push
|
|
26
|
+
that evaluated to false), the image exists only in the runner's local Docker daemon — not
|
|
27
|
+
in the remote registry. The attestation step then fails with a 404 when fetching the
|
|
28
|
+
manifest from the registry URL.
|
|
29
|
+
|
|
30
|
+
This is a silent misconfiguration: the build step "succeeds" (because `load: true` works),
|
|
31
|
+
the attestation step then fails with a cryptic OCI/manifest error instead of a clear message
|
|
32
|
+
explaining that the image was never pushed.
|
|
33
|
+
|
|
34
|
+
Typical trigger: workflows that conditionally push (e.g., only on `main` branch) but run
|
|
35
|
+
the attestation step unconditionally on every push/PR.
|
|
36
|
+
fix: |
|
|
37
|
+
Guard the attestation step with the same `if:` condition used for the image push.
|
|
38
|
+
The attestation step should only run when the image was actually pushed to the registry.
|
|
39
|
+
|
|
40
|
+
If you need to generate attestations on PRs (e.g., for preview images), ensure the image
|
|
41
|
+
is actually pushed to a staging registry before the attestation step runs.
|
|
42
|
+
fix_code:
|
|
43
|
+
- language: yaml
|
|
44
|
+
label: "Wrong: attestation runs unconditionally even when image not pushed"
|
|
45
|
+
code: |
|
|
46
|
+
- name: Build and push
|
|
47
|
+
id: build-push
|
|
48
|
+
uses: docker/build-push-action@v6
|
|
49
|
+
with:
|
|
50
|
+
push: ${{ github.ref == 'refs/heads/main' }} # Only pushes on main
|
|
51
|
+
load: true
|
|
52
|
+
tags: ghcr.io/org/app:latest
|
|
53
|
+
|
|
54
|
+
- name: Generate attestation
|
|
55
|
+
uses: actions/attest-build-provenance@v2
|
|
56
|
+
with:
|
|
57
|
+
subject-name: ghcr.io/org/app
|
|
58
|
+
subject-digest: ${{ steps.build-push.outputs.digest }}
|
|
59
|
+
push-to-registry: true # ❌ Fails on PRs — image is local-only
|
|
60
|
+
- language: yaml
|
|
61
|
+
label: "Fix: match attestation if condition to the push condition"
|
|
62
|
+
code: |
|
|
63
|
+
- name: Build and push
|
|
64
|
+
id: build-push
|
|
65
|
+
uses: docker/build-push-action@v6
|
|
66
|
+
with:
|
|
67
|
+
push: ${{ github.ref == 'refs/heads/main' }}
|
|
68
|
+
load: true
|
|
69
|
+
tags: ghcr.io/org/app:latest
|
|
70
|
+
|
|
71
|
+
- name: Generate attestation
|
|
72
|
+
if: github.ref == 'refs/heads/main' # ✅ Same condition as push
|
|
73
|
+
uses: actions/attest-build-provenance@v2
|
|
74
|
+
with:
|
|
75
|
+
subject-name: ghcr.io/org/app
|
|
76
|
+
subject-digest: ${{ steps.build-push.outputs.digest }}
|
|
77
|
+
push-to-registry: true
|
|
78
|
+
- language: yaml
|
|
79
|
+
label: "Alternative: use push-to-registry: false for GitHub-only attestations"
|
|
80
|
+
code: |
|
|
81
|
+
- name: Build and push
|
|
82
|
+
id: build-push
|
|
83
|
+
uses: docker/build-push-action@v6
|
|
84
|
+
with:
|
|
85
|
+
push: true
|
|
86
|
+
tags: ghcr.io/org/app:latest
|
|
87
|
+
|
|
88
|
+
- name: Generate attestation (stored in GitHub, not registry)
|
|
89
|
+
uses: actions/attest-build-provenance@v2
|
|
90
|
+
with:
|
|
91
|
+
subject-name: ghcr.io/org/app
|
|
92
|
+
subject-digest: ${{ steps.build-push.outputs.digest }}
|
|
93
|
+
push-to-registry: false # ✅ Default; stores attestation in GitHub only
|
|
94
|
+
prevention:
|
|
95
|
+
- "Always guard `push-to-registry: true` attestation steps with the same `if:` condition used for the image push"
|
|
96
|
+
- "Remember: `load: true` in docker/build-push-action loads the image into the local Docker daemon only — it does not push to any registry"
|
|
97
|
+
- "Use `push-to-registry: false` (the default) when attestations only need to be verified via `gh attestation verify`, not via the registry manifest"
|
|
98
|
+
- "Set `push: ${{ steps.should-push.outputs.result }}` and reuse that output in the attestation step's `if:` condition to keep them in sync"
|
|
99
|
+
docs:
|
|
100
|
+
- url: "https://github.com/actions/attest-build-provenance"
|
|
101
|
+
label: "actions/attest-build-provenance README"
|
|
102
|
+
- url: "https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds"
|
|
103
|
+
label: "Using artifact attestations to establish provenance for builds"
|
|
104
|
+
- url: "https://github.com/actions/attest-build-provenance/issues/747"
|
|
105
|
+
label: "actions/attest-build-provenance#747: 404 when uploading artifact to container registry"
|
|
@@ -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"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
id: triggers-018
|
|
2
|
+
title: "workflow_run: download-artifact finds no artifacts without explicit run-id"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- workflow_run
|
|
7
|
+
- download-artifact
|
|
8
|
+
- run-id
|
|
9
|
+
- cross-workflow
|
|
10
|
+
- artifact
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "No artifacts found"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "Unable to find any artifacts for the associated workflow"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "No artifacts were found with the provided run ID"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "No artifacts found"
|
|
20
|
+
- "Unable to find any artifacts for the associated workflow run"
|
|
21
|
+
- "Warning: No artifacts were found with the provided run ID."
|
|
22
|
+
- "Error: Unable to find any artifacts for the associated workflow run"
|
|
23
|
+
root_cause: |
|
|
24
|
+
When a workflow is triggered by the `workflow_run` event, it runs in the context of
|
|
25
|
+
**its own** workflow run — not the triggering workflow run. If `actions/download-artifact`
|
|
26
|
+
is called without specifying `run-id`, it defaults to `${{ github.run_id }}` which is the
|
|
27
|
+
ID of the triggered (consumer) workflow — not the upstream workflow that produced the
|
|
28
|
+
artifacts.
|
|
29
|
+
|
|
30
|
+
Since the consumer workflow produces no artifacts itself, `download-artifact` finds nothing.
|
|
31
|
+
This is a silent failure in some versions: the step exits without error but no files are
|
|
32
|
+
downloaded, causing subsequent steps that depend on the artifact to fail with confusing errors.
|
|
33
|
+
|
|
34
|
+
The artifact was uploaded in the triggering workflow's run. To download it, you must
|
|
35
|
+
explicitly reference `github.event.workflow_run.id` (the upstream run's ID).
|
|
36
|
+
fix: |
|
|
37
|
+
Add `run-id: ${{ github.event.workflow_run.id }}` to the `actions/download-artifact`
|
|
38
|
+
step. This tells the action to download from the triggering workflow run rather than
|
|
39
|
+
the current workflow run.
|
|
40
|
+
|
|
41
|
+
Also ensure the token has `actions: read` permission to download artifacts from other runs.
|
|
42
|
+
fix_code:
|
|
43
|
+
- language: yaml
|
|
44
|
+
label: "Wrong: download-artifact defaults to current run (finds nothing in workflow_run)"
|
|
45
|
+
code: |
|
|
46
|
+
on:
|
|
47
|
+
workflow_run:
|
|
48
|
+
workflows: ["CI"]
|
|
49
|
+
types: [completed]
|
|
50
|
+
|
|
51
|
+
jobs:
|
|
52
|
+
deploy:
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/download-artifact@v4
|
|
56
|
+
with:
|
|
57
|
+
name: build-output # ❌ Defaults to github.run_id (this run, has no artifacts)
|
|
58
|
+
- language: yaml
|
|
59
|
+
label: "Fix: explicitly reference the triggering run's ID"
|
|
60
|
+
code: |
|
|
61
|
+
on:
|
|
62
|
+
workflow_run:
|
|
63
|
+
workflows: ["CI"]
|
|
64
|
+
types: [completed]
|
|
65
|
+
|
|
66
|
+
jobs:
|
|
67
|
+
deploy:
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
permissions:
|
|
70
|
+
actions: read # Required to download artifacts from other workflow runs
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/download-artifact@v4
|
|
73
|
+
with:
|
|
74
|
+
name: build-output
|
|
75
|
+
run-id: ${{ github.event.workflow_run.id }} # ✅ The upstream run's ID
|
|
76
|
+
- language: yaml
|
|
77
|
+
label: "Guard: only run if triggering workflow succeeded"
|
|
78
|
+
code: |
|
|
79
|
+
on:
|
|
80
|
+
workflow_run:
|
|
81
|
+
workflows: ["CI"]
|
|
82
|
+
types: [completed]
|
|
83
|
+
|
|
84
|
+
jobs:
|
|
85
|
+
deploy:
|
|
86
|
+
if: github.event.workflow_run.conclusion == 'success' # ✅ Don't deploy on failure
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
permissions:
|
|
89
|
+
actions: read
|
|
90
|
+
steps:
|
|
91
|
+
- uses: actions/download-artifact@v4
|
|
92
|
+
with:
|
|
93
|
+
name: build-output
|
|
94
|
+
run-id: ${{ github.event.workflow_run.id }}
|
|
95
|
+
prevention:
|
|
96
|
+
- "Always specify `run-id: ${{ github.event.workflow_run.id }}` when downloading artifacts in a `workflow_run`-triggered workflow"
|
|
97
|
+
- "Add `permissions: actions: read` to any job that downloads artifacts from a different workflow run"
|
|
98
|
+
- "Guard the job with `if: github.event.workflow_run.conclusion == 'success'` to skip on upstream failures"
|
|
99
|
+
- "Use `github.event.workflow_run.id` (not `github.run_id`) — they are different: the former is the upstream run, the latter is the current consumer run"
|
|
100
|
+
docs:
|
|
101
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run"
|
|
102
|
+
label: "Events that trigger workflows — workflow_run"
|
|
103
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/storing-and-sharing-data-from-a-workflow#downloading-artifacts-from-a-different-workflow-run"
|
|
104
|
+
label: "Downloading artifacts from a different workflow run"
|
|
105
|
+
- url: "https://github.com/actions/download-artifact"
|
|
106
|
+
label: "actions/download-artifact README"
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
id: yaml-syntax-023
|
|
2
|
+
title: "Reusable workflow: env context rejected in `jobs.<job_id>.with` inputs"
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- reusable-workflow
|
|
7
|
+
- env-context
|
|
8
|
+
- with-inputs
|
|
9
|
+
- workflow_call
|
|
10
|
+
- variable-scoping
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "Unrecognized named-value: 'env'"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "Context access might be invalid: env"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "The env context is not available"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "Unrecognized named-value: 'env'. Located at position 1 within expression: env.MY_VAR"
|
|
20
|
+
- "Context access might be invalid: env"
|
|
21
|
+
- "The env context is not available to reusable workflow inputs"
|
|
22
|
+
root_cause: |
|
|
23
|
+
When calling a reusable workflow using `jobs.<job_id>.uses`, the `env` context is not
|
|
24
|
+
available inside the `with:` block. Only the following contexts are permitted at that
|
|
25
|
+
evaluation point: `github`, `inputs`, `needs`, `strategy`, and `matrix`.
|
|
26
|
+
|
|
27
|
+
This is a platform-level restriction. The `env` context is resolved during job execution
|
|
28
|
+
on the runner, but reusable workflow `with:` inputs are evaluated at workflow dispatch time
|
|
29
|
+
(before a runner is allocated), so `env` values are simply unavailable.
|
|
30
|
+
|
|
31
|
+
Top-level `env:` blocks defined in the caller workflow cannot be referenced inside
|
|
32
|
+
`jobs.<job_id>.with` — using `${{ env.MY_VAR }}` there produces a syntax validation
|
|
33
|
+
error that prevents the workflow from running at all.
|
|
34
|
+
fix: |
|
|
35
|
+
Replace `${{ env.MY_VAR }}` in reusable workflow `with:` inputs with one of:
|
|
36
|
+
|
|
37
|
+
1. `${{ vars.MY_VAR }}` — repository or organization variable (preferred for non-secret
|
|
38
|
+
configuration values that are reused across workflows)
|
|
39
|
+
2. Hardcoded literal value directly in `with:`
|
|
40
|
+
3. An intermediate job that exposes the value as a job output, then reference it via
|
|
41
|
+
`${{ needs.prepare.outputs.my_var }}`
|
|
42
|
+
fix_code:
|
|
43
|
+
- language: yaml
|
|
44
|
+
label: "Wrong: env context in reusable workflow with inputs"
|
|
45
|
+
code: |
|
|
46
|
+
env:
|
|
47
|
+
DEPLOY_ENV: "production"
|
|
48
|
+
|
|
49
|
+
jobs:
|
|
50
|
+
deploy:
|
|
51
|
+
uses: org/repo/.github/workflows/deploy.yml@main
|
|
52
|
+
with:
|
|
53
|
+
environment: ${{ env.DEPLOY_ENV }} # ❌ Error: env context not available
|
|
54
|
+
- language: yaml
|
|
55
|
+
label: "Fix option 1: use vars context (repository/org variable)"
|
|
56
|
+
code: |
|
|
57
|
+
jobs:
|
|
58
|
+
deploy:
|
|
59
|
+
uses: org/repo/.github/workflows/deploy.yml@main
|
|
60
|
+
with:
|
|
61
|
+
environment: ${{ vars.DEPLOY_ENV }} # ✅ vars context works in with:
|
|
62
|
+
- language: yaml
|
|
63
|
+
label: "Fix option 2: propagate via intermediate job output"
|
|
64
|
+
code: |
|
|
65
|
+
env:
|
|
66
|
+
DEPLOY_ENV: "production"
|
|
67
|
+
|
|
68
|
+
jobs:
|
|
69
|
+
prepare:
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
outputs:
|
|
72
|
+
deploy_env: ${{ steps.set-env.outputs.value }}
|
|
73
|
+
steps:
|
|
74
|
+
- id: set-env
|
|
75
|
+
run: echo "value=$DEPLOY_ENV" >> "$GITHUB_OUTPUT"
|
|
76
|
+
|
|
77
|
+
deploy:
|
|
78
|
+
needs: prepare
|
|
79
|
+
uses: org/repo/.github/workflows/deploy.yml@main
|
|
80
|
+
with:
|
|
81
|
+
environment: ${{ needs.prepare.outputs.deploy_env }} # ✅
|
|
82
|
+
- language: yaml
|
|
83
|
+
label: "Fix option 3: hardcode the value directly"
|
|
84
|
+
code: |
|
|
85
|
+
jobs:
|
|
86
|
+
deploy:
|
|
87
|
+
uses: org/repo/.github/workflows/deploy.yml@main
|
|
88
|
+
with:
|
|
89
|
+
environment: "production" # ✅ Literal values always work
|
|
90
|
+
prevention:
|
|
91
|
+
- "Store workflow-wide configuration values in repository or organization variables (`vars` context) so they are available in reusable workflow `with:` blocks"
|
|
92
|
+
- "Only `github`, `inputs`, `needs`, `strategy`, and `matrix` contexts are available in `jobs.<job_id>.with` — never `env`, `secrets`, or `steps`"
|
|
93
|
+
- "If a value must be computed at runtime, use an intermediate job with `outputs:` and reference via `needs.<job_id>.outputs.<key>`"
|
|
94
|
+
docs:
|
|
95
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/reusing-workflows#limitations"
|
|
96
|
+
label: "Reusing workflows — Limitations"
|
|
97
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#context-availability"
|
|
98
|
+
label: "GitHub Actions context availability by workflow key"
|
|
99
|
+
- url: "https://github.com/actions/runner/issues/1413"
|
|
100
|
+
label: "actions/runner#1413: env not available in reusable workflow with inputs (known limitation)"
|
|
101
|
+
- url: "https://github.com/actions/toolkit/issues/931"
|
|
102
|
+
label: "actions/toolkit#931: Variable scoping across reusable workflows (58 reactions)"
|
package/package.json
CHANGED