@htekdev/actions-debugger 1.0.132 → 1.0.134
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/errors/caching-artifacts/caching-artifacts-078.yml +100 -0
- package/errors/caching-artifacts/caching-artifacts-079.yml +95 -0
- package/errors/known-unsolved/known-unsolved-077.yml +140 -0
- package/errors/known-unsolved/known-unsolved-078.yml +91 -0
- package/errors/runner-environment/runner-environment-246.yml +120 -0
- package/errors/runner-environment/runner-environment-247.yml +73 -0
- package/errors/runner-environment/runner-environment-248.yml +106 -0
- package/errors/silent-failures/silent-failures-122.yml +102 -0
- package/package.json +1 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
id: caching-artifacts-078
|
|
2
|
+
title: '`upload-artifact/merge` aborts with "Failed to DeleteArtifact: (404) Not Found" when a delete is retried after a transient server error'
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- upload-artifact
|
|
7
|
+
- merge
|
|
8
|
+
- delete-merged
|
|
9
|
+
- 404
|
|
10
|
+
- idempotent-delete
|
|
11
|
+
- artifact-merge
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Failed to DeleteArtifact.*non-retryable error.*404|DeleteArtifact.*404.*Not Found'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'Received non-retryable error.*Failed request.*404.*artifact not found'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Error: Failed to DeleteArtifact: Received non-retryable error: Failed request: (404) Not Found: artifact not found"
|
|
19
|
+
- "Attempt 1 of 5 failed with error: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON. Retrying request in 3000 ms..."
|
|
20
|
+
root_cause: |
|
|
21
|
+
When using `actions/upload-artifact/merge@v6` (or later) with `delete-merged: true`,
|
|
22
|
+
the action deletes each source artifact after merging them into the combined artifact.
|
|
23
|
+
If a delete request encounters a transient server error (e.g., a malformed HTML response
|
|
24
|
+
instead of JSON — indicated by "Unexpected token '<'"), the action retries the delete.
|
|
25
|
+
|
|
26
|
+
If the first delete request was actually processed by the server before the error was
|
|
27
|
+
returned to the client, the artifact is already gone. The retry then receives a 404
|
|
28
|
+
Not Found, which `DeleteArtifact` treats as a **non-retryable fatal error** and
|
|
29
|
+
immediately aborts the job.
|
|
30
|
+
|
|
31
|
+
Because `delete-merged: true` is destructive — source artifacts have already been
|
|
32
|
+
deleted by this point — retrying the entire job from scratch is impossible, as the
|
|
33
|
+
source artifacts no longer exist.
|
|
34
|
+
|
|
35
|
+
Root bug: `DeleteArtifact` should treat 404 as a success (idempotent delete — the
|
|
36
|
+
artifact not existing IS the desired state). This is tracked in upload-artifact#751
|
|
37
|
+
(open, Jan 2026). No server-side fix has been released as of June 2026.
|
|
38
|
+
fix: |
|
|
39
|
+
Option 1 — Add `continue-on-error: true` to the merge step so that the 404 error
|
|
40
|
+
does not fail the overall job. Verify that the merge artifact was created successfully
|
|
41
|
+
before relying on this workaround, since `continue-on-error` also swallows real
|
|
42
|
+
failures.
|
|
43
|
+
|
|
44
|
+
Option 2 — Set `delete-merged: false` and handle deletion of source artifacts in a
|
|
45
|
+
separate step with explicit error handling (e.g., `gh api` with `--fail-with-body` and
|
|
46
|
+
a conditional step that ignores 404 exit codes).
|
|
47
|
+
|
|
48
|
+
Option 3 — Wrap the merge step in a retry loop using `nick-invision/retry` or a
|
|
49
|
+
shell retry, but note that re-running the merge after partial deletion will fail because
|
|
50
|
+
source artifacts are gone. Prevention is better than recovery here.
|
|
51
|
+
|
|
52
|
+
Monitor upload-artifact#751 for an upstream fix that makes 404 on DeleteArtifact
|
|
53
|
+
idempotent/non-fatal.
|
|
54
|
+
fix_code:
|
|
55
|
+
- language: yaml
|
|
56
|
+
label: 'continue-on-error workaround — prevents 404 from failing the job'
|
|
57
|
+
code: |
|
|
58
|
+
- name: Merge artifacts
|
|
59
|
+
uses: actions/upload-artifact/merge@v6
|
|
60
|
+
# ⚠️ Workaround: continue-on-error so a DeleteArtifact 404 does not fail the job.
|
|
61
|
+
# Also swallows real upload failures — add a downstream step to verify the
|
|
62
|
+
# merged artifact exists if reliability matters.
|
|
63
|
+
continue-on-error: true
|
|
64
|
+
with:
|
|
65
|
+
name: merged-results
|
|
66
|
+
pattern: partial-results-*
|
|
67
|
+
delete-merged: true
|
|
68
|
+
- language: yaml
|
|
69
|
+
label: 'delete-merged: false with manual deletion that ignores 404'
|
|
70
|
+
code: |
|
|
71
|
+
- name: Merge artifacts (no auto-delete)
|
|
72
|
+
uses: actions/upload-artifact/merge@v6
|
|
73
|
+
with:
|
|
74
|
+
name: merged-results
|
|
75
|
+
pattern: partial-results-*
|
|
76
|
+
delete-merged: false # ✅ Suppress automatic deletion
|
|
77
|
+
|
|
78
|
+
# Manually delete source artifacts, ignoring 404 responses (already deleted)
|
|
79
|
+
- name: Delete source artifacts
|
|
80
|
+
env:
|
|
81
|
+
GH_TOKEN: ${{ github.token }}
|
|
82
|
+
run: |
|
|
83
|
+
for artifact_id in $(gh api repos/${{ github.repository }}/actions/artifacts \
|
|
84
|
+
--jq '.artifacts[] | select(.name | startswith("partial-results-")) | .id'); do
|
|
85
|
+
gh api --method DELETE \
|
|
86
|
+
repos/${{ github.repository }}/actions/artifacts/$artifact_id \
|
|
87
|
+
--silent || echo "Artifact $artifact_id already deleted (404) — ignoring"
|
|
88
|
+
done
|
|
89
|
+
prevention:
|
|
90
|
+
- 'Avoid `delete-merged: true` in critical CI pipelines until upload-artifact#751 is fixed — use manual deletion with 404 tolerance instead'
|
|
91
|
+
- 'Add `continue-on-error: true` to merge steps as a temporary workaround for the 404 abort'
|
|
92
|
+
- 'Keep source artifacts available by using `delete-merged: false` until the upstream fix lands'
|
|
93
|
+
- 'Monitor actions/upload-artifact#751 for a release that treats DeleteArtifact 404 as success'
|
|
94
|
+
docs:
|
|
95
|
+
- url: 'https://github.com/actions/upload-artifact/issues/751'
|
|
96
|
+
label: 'upload-artifact#751: DeleteArtifact should not consider a 404 response an error (open, Jan 2026)'
|
|
97
|
+
- url: 'https://github.com/actions/upload-artifact/blob/main/merge/README.md'
|
|
98
|
+
label: 'actions/upload-artifact merge action README — delete-merged option'
|
|
99
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/storing-workflow-data-as-artifacts'
|
|
100
|
+
label: 'GitHub Docs: Storing workflow data as artifacts'
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
id: caching-artifacts-079
|
|
2
|
+
title: '`setup-node@v5` auto-enables caching when `packageManager` is set in `package.json` — post-run fails with "Path Validation Error" if dependencies are not installed'
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- setup-node
|
|
7
|
+
- caching
|
|
8
|
+
- packageManager
|
|
9
|
+
- path-validation
|
|
10
|
+
- post-run
|
|
11
|
+
- setup-node-v5
|
|
12
|
+
- automatic-caching
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'Path Validation Error.*Path.*specified.*action.*caching.*do.*not exist'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'Path.*specified.*action.*caching.*do.{0,5}not exist.*no cache'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
error_messages:
|
|
19
|
+
- "Error: Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved."
|
|
20
|
+
root_cause: |
|
|
21
|
+
Starting with `actions/setup-node@v5`, automatic caching is enabled **by default**
|
|
22
|
+
whenever a `packageManager` field is present in the project's `package.json` file —
|
|
23
|
+
even if the workflow does not explicitly configure caching via the `cache:` input.
|
|
24
|
+
|
|
25
|
+
This means jobs that merely run linting, type-checking, or any step that does not
|
|
26
|
+
execute `npm install` / `yarn install` / `pnpm install` will still attempt to cache
|
|
27
|
+
the package manager's dependency directory in the post-run cleanup step. Because no
|
|
28
|
+
`install` was run, the expected cache paths (e.g., `~/.npm`, `~/.yarn/cache`,
|
|
29
|
+
`~/.pnpm-store`) do not exist. The toolkit's path validation then emits:
|
|
30
|
+
"Path Validation Error: Path(s) specified in the action for caching do(es) not exist"
|
|
31
|
+
and **fails the job step**, turning a previously green run red.
|
|
32
|
+
|
|
33
|
+
**Why this surprises developers:**
|
|
34
|
+
- The workflow never explicitly enables caching — the presence of `packageManager`
|
|
35
|
+
in `package.json` is the invisible trigger.
|
|
36
|
+
- The error appears in the **post-run** cleanup step, not in the setup step where
|
|
37
|
+
caching is configured, making it harder to trace.
|
|
38
|
+
- Workflows that have always worked (no `cache:` input) start failing after upgrading
|
|
39
|
+
from `setup-node@v4` to `setup-node@v5`.
|
|
40
|
+
|
|
41
|
+
**Resolution:** `setup-node@v6` (released Oct 2025) changed the default so automatic
|
|
42
|
+
caching only applies to npm; yarn/pnpm caching is now opt-in. Upgrading to v6 or
|
|
43
|
+
explicitly disabling the cache in v5 resolves the issue.
|
|
44
|
+
Source: setup-node#1363 (5 reactions, closed Oct 2025 via v6 release).
|
|
45
|
+
fix: |
|
|
46
|
+
Option 1 (recommended) — Upgrade to `actions/setup-node@v6`. The v6 release limits
|
|
47
|
+
automatic caching to npm only; yarn and pnpm caching require explicit `cache: yarn`
|
|
48
|
+
or `cache: pnpm` input.
|
|
49
|
+
|
|
50
|
+
Option 2 — Disable automatic caching in v5 by setting `package-manager-cache: false`.
|
|
51
|
+
Use this if you cannot upgrade to v6 immediately.
|
|
52
|
+
|
|
53
|
+
Option 3 — Ensure dependencies are installed in every job that uses `setup-node@v5`.
|
|
54
|
+
If the job only needs Node for tooling (lint, type-check, etc.), run a minimal
|
|
55
|
+
`npm ci --ignore-scripts` or equivalent before the setup post-run fires.
|
|
56
|
+
fix_code:
|
|
57
|
+
- language: yaml
|
|
58
|
+
label: 'Upgrade to setup-node@v6 — fixes automatic caching defaults'
|
|
59
|
+
code: |
|
|
60
|
+
# ✅ Recommended: v6 only auto-caches npm, not yarn/pnpm
|
|
61
|
+
- uses: actions/setup-node@v6
|
|
62
|
+
with:
|
|
63
|
+
node-version: '22'
|
|
64
|
+
# No cache: input needed — auto-caching is npm-only in v6
|
|
65
|
+
- language: yaml
|
|
66
|
+
label: 'Disable automatic caching in v5 with package-manager-cache: false'
|
|
67
|
+
code: |
|
|
68
|
+
# ✅ Workaround for setup-node@v5: suppress auto-caching
|
|
69
|
+
- uses: actions/setup-node@v5
|
|
70
|
+
with:
|
|
71
|
+
node-version: '22'
|
|
72
|
+
package-manager-cache: false # Disables packageManager-triggered auto-cache
|
|
73
|
+
- run: npx eslint src/
|
|
74
|
+
- language: yaml
|
|
75
|
+
label: 'Opt in to caching explicitly for jobs that do install dependencies'
|
|
76
|
+
code: |
|
|
77
|
+
# ✅ Jobs that do install: use explicit cache input instead of relying on auto-detect
|
|
78
|
+
- uses: actions/setup-node@v6
|
|
79
|
+
with:
|
|
80
|
+
node-version: '22'
|
|
81
|
+
cache: 'npm' # Explicit opt-in — cache only when deps are installed
|
|
82
|
+
- run: npm ci
|
|
83
|
+
- run: npm test
|
|
84
|
+
prevention:
|
|
85
|
+
- 'Upgrade to `setup-node@v6` which fixes the over-eager auto-caching of yarn/pnpm in v5'
|
|
86
|
+
- 'In v5, always set `package-manager-cache: false` for jobs that do not run `install`'
|
|
87
|
+
- 'Check `package.json` for a `packageManager` field — its presence triggers auto-caching in v5 even without an explicit `cache:` input'
|
|
88
|
+
- 'Run lint, type-check, and audit jobs without setup-node caching to keep them fast and avoid phantom cache failures'
|
|
89
|
+
docs:
|
|
90
|
+
- url: 'https://github.com/actions/setup-node/issues/1363'
|
|
91
|
+
label: 'setup-node#1363: v5 fails with Path Validation Error in Post Run steps (5 reactions, resolved in v6)'
|
|
92
|
+
- url: 'https://github.com/actions/setup-node/releases/tag/v6.0.0'
|
|
93
|
+
label: 'setup-node v6.0.0 release notes — auto-caching limited to npm'
|
|
94
|
+
- url: 'https://github.com/actions/setup-node/blob/main/docs/advanced-usage.md#caching-packages-data'
|
|
95
|
+
label: 'setup-node docs: Caching packages data'
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
id: known-unsolved-077
|
|
2
|
+
title: '`fromJSON()` array nested inside a matrix object does not expand — resolves as literal "Array" or raw JSON string'
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- matrix
|
|
7
|
+
- fromJSON
|
|
8
|
+
- dynamic-matrix
|
|
9
|
+
- object
|
|
10
|
+
- array-expansion
|
|
11
|
+
- known-limitation
|
|
12
|
+
- silent-failure
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'fromJSON\(needs\.\w+\.outputs\.'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'matrix\.\w+\.\w+.*\bArray\b|\bArray\b.*matrix\.'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
error_messages:
|
|
19
|
+
- "echo x86_64 Array"
|
|
20
|
+
- 'matrix.component.distro = Array'
|
|
21
|
+
- 'matrix.component.distro = ["el8","el9"]'
|
|
22
|
+
root_cause: |
|
|
23
|
+
GitHub Actions supports expanding a JSON array into matrix rows when `fromJSON()` is
|
|
24
|
+
used **at the top level** of a matrix dimension:
|
|
25
|
+
```yaml
|
|
26
|
+
strategy:
|
|
27
|
+
matrix:
|
|
28
|
+
version: ${{ fromJSON(needs.setup.outputs.versions) }} # ✅ expands to N rows
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
However, when the array is nested inside a matrix **object** (i.e., as a value within
|
|
32
|
+
one of the items of a matrix dimension), `fromJSON()` does NOT expand it into rows.
|
|
33
|
+
Instead, the expression resolves to:
|
|
34
|
+
- The literal string `"Array"` when `fromJSON()` is used (type-1 pattern)
|
|
35
|
+
- The raw JSON string (e.g., `["el8","el9"]`) when the output is used directly (type-2 pattern)
|
|
36
|
+
|
|
37
|
+
**Failing patterns:**
|
|
38
|
+
```yaml
|
|
39
|
+
# Type 1 — fromJSON inside a matrix object value
|
|
40
|
+
matrix:
|
|
41
|
+
component:
|
|
42
|
+
- name: rhel
|
|
43
|
+
distro: ${{ fromJSON(needs.setup.outputs.rpms) }} # ❌ becomes "Array"
|
|
44
|
+
|
|
45
|
+
# Type 2 — raw JSON string inside a matrix object value
|
|
46
|
+
matrix:
|
|
47
|
+
component:
|
|
48
|
+
- name: rhel
|
|
49
|
+
distro: ${{ needs.setup.outputs.rpms }} # ❌ becomes the raw JSON string
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The expansion mechanism only applies when the entire matrix dimension value is a
|
|
53
|
+
`fromJSON()` call returning an array. Nesting breaks the expansion because the runner
|
|
54
|
+
evaluates the object first and cannot retroactively fan out a single object item into
|
|
55
|
+
multiple rows.
|
|
56
|
+
|
|
57
|
+
This is a documented limitation. GitHub's matrix engine does not support nested array
|
|
58
|
+
expansion within object-typed matrix dimensions. The issue is tracked in
|
|
59
|
+
actions/runner#3794 (open since Apr 2025, no planned fix).
|
|
60
|
+
|
|
61
|
+
**Why this is confusing:**
|
|
62
|
+
The `fromJSON()` approach works perfectly when the array IS the entire dimension:
|
|
63
|
+
`matrix: version: ${{ fromJSON(needs.setup.outputs.versions) }}`. Developers naturally
|
|
64
|
+
try to reuse the same pattern inside object dimensions and get silently wrong values —
|
|
65
|
+
the job runs to completion but with a single row containing "Array" instead of N rows.
|
|
66
|
+
fix: |
|
|
67
|
+
Restructure the matrix so that the dynamic array is the TOP-LEVEL matrix dimension,
|
|
68
|
+
not nested inside an object. Move any additional object properties into the
|
|
69
|
+
`matrix.include` block to pair them with each array element.
|
|
70
|
+
|
|
71
|
+
If you need multiple outputs to compose a matrix, pre-process them into a combined
|
|
72
|
+
JSON array in an upstream job step and pass the combined output as a single
|
|
73
|
+
`fromJSON()` expression.
|
|
74
|
+
fix_code:
|
|
75
|
+
- language: yaml
|
|
76
|
+
label: 'Move the dynamic array to the top-level dimension — this expands correctly'
|
|
77
|
+
code: |
|
|
78
|
+
jobs:
|
|
79
|
+
setup:
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
outputs:
|
|
82
|
+
rpms: ${{ steps.distros.outputs.rpms }}
|
|
83
|
+
debs: ${{ steps.distros.outputs.debs }}
|
|
84
|
+
steps:
|
|
85
|
+
- id: distros
|
|
86
|
+
run: |
|
|
87
|
+
echo 'rpms=["el8","el9"]' >> "$GITHUB_OUTPUT"
|
|
88
|
+
echo 'debs=["focal","jammy","noble"]' >> "$GITHUB_OUTPUT"
|
|
89
|
+
|
|
90
|
+
build:
|
|
91
|
+
needs: setup
|
|
92
|
+
runs-on: ubuntu-latest
|
|
93
|
+
strategy:
|
|
94
|
+
matrix:
|
|
95
|
+
# ✅ distro is the top-level dimension — fromJSON expands to N rows
|
|
96
|
+
distro: ${{ fromJSON(needs.setup.outputs.rpms) }}
|
|
97
|
+
steps:
|
|
98
|
+
- run: echo "Building for ${{ matrix.distro }}"
|
|
99
|
+
- language: yaml
|
|
100
|
+
label: 'Pre-combine multiple arrays into a single include list in the upstream job'
|
|
101
|
+
code: |
|
|
102
|
+
jobs:
|
|
103
|
+
setup:
|
|
104
|
+
runs-on: ubuntu-latest
|
|
105
|
+
outputs:
|
|
106
|
+
matrix: ${{ steps.build-matrix.outputs.matrix }}
|
|
107
|
+
steps:
|
|
108
|
+
- id: build-matrix
|
|
109
|
+
run: |
|
|
110
|
+
# Build a combined include list from multiple arrays
|
|
111
|
+
matrix=$(python3 -c "
|
|
112
|
+
import json, sys
|
|
113
|
+
rpms = ['el8', 'el9']
|
|
114
|
+
debs = ['focal', 'jammy', 'noble']
|
|
115
|
+
include = [{'family': 'rhel', 'distro': d} for d in rpms] + \
|
|
116
|
+
[{'family': 'debian', 'distro': d} for d in debs]
|
|
117
|
+
print(json.dumps({'include': include}))
|
|
118
|
+
")
|
|
119
|
+
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
|
|
120
|
+
|
|
121
|
+
build:
|
|
122
|
+
needs: setup
|
|
123
|
+
runs-on: ubuntu-latest
|
|
124
|
+
strategy:
|
|
125
|
+
# ✅ fromJSON on a single combined matrix object with include list
|
|
126
|
+
matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
|
|
127
|
+
steps:
|
|
128
|
+
- run: echo "Building ${{ matrix.family }} for ${{ matrix.distro }}"
|
|
129
|
+
prevention:
|
|
130
|
+
- 'Never use `fromJSON()` as the value of a property inside a matrix object — it will not expand to multiple rows'
|
|
131
|
+
- 'Only use `fromJSON()` when the array IS the entire dimension value (e.g., `matrix.version: ${{ fromJSON(...) }}`)'
|
|
132
|
+
- 'When combining multi-dimensional dynamic matrices, pre-compute a combined `include` list in an upstream job'
|
|
133
|
+
- 'Verify matrix expansion by adding a `- run: echo ${{ toJSON(matrix) }}` debug step; "Array" in the output confirms the bug'
|
|
134
|
+
docs:
|
|
135
|
+
- url: 'https://github.com/actions/runner/issues/3794'
|
|
136
|
+
label: 'actions/runner#3794: Array outputs not understood by matrix when nested inside object (open, Apr 2025)'
|
|
137
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow#using-a-matrix-strategy'
|
|
138
|
+
label: 'GitHub Docs: Using a matrix strategy'
|
|
139
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#fromjson'
|
|
140
|
+
label: 'GitHub Docs: fromJSON expression function'
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
id: known-unsolved-078
|
|
2
|
+
title: 'Copilot agent branches not available in `workflow_dispatch` branch picker — cannot manually target Copilot branches'
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- workflow_dispatch
|
|
7
|
+
- copilot-agent
|
|
8
|
+
- branch-picker
|
|
9
|
+
- manual-trigger
|
|
10
|
+
- github-copilot
|
|
11
|
+
- ui-limitation
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'copilot.*branch.*not.*available|copilot.*branch.*missing.*dispatch'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'workflow_dispatch.*copilot.*branch|copilot.*agent.*branch.*trigger'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "# No error message — Copilot agent branches simply do not appear in the branch selector"
|
|
19
|
+
root_cause: |
|
|
20
|
+
When GitHub Copilot coding agent creates a branch to work on an assigned task,
|
|
21
|
+
the branch uses a namespaced prefix (`copilot/` by default, e.g.,
|
|
22
|
+
`copilot/fix-issue-123`). These branches are created and owned by the Copilot agent.
|
|
23
|
+
|
|
24
|
+
The GitHub Actions UI for `workflow_dispatch` presents a branch picker dropdown
|
|
25
|
+
that allows users to select which branch to run the workflow against. However,
|
|
26
|
+
Copilot agent branches are NOT included in this branch picker.
|
|
27
|
+
|
|
28
|
+
This is a UI-level platform limitation: GitHub filters the branch list shown in the
|
|
29
|
+
`workflow_dispatch` selector and excludes Copilot-owned branches from the list.
|
|
30
|
+
There is no documented API reason why this would be necessary — the branches exist
|
|
31
|
+
and are valid git refs — but the UI omits them.
|
|
32
|
+
|
|
33
|
+
As a workaround, the GitHub CLI or REST API CAN be used to trigger a
|
|
34
|
+
`workflow_dispatch` run targeting a Copilot branch by supplying the `ref` parameter
|
|
35
|
+
explicitly (bypassing the UI picker).
|
|
36
|
+
|
|
37
|
+
This issue was reported in runner#4246 (Feb 2026, 15 reactions) and remained open
|
|
38
|
+
as of June 2026. No ETA for a fix has been provided.
|
|
39
|
+
|
|
40
|
+
Note: This is a separate issue from Copilot agent PR workflows requiring approval
|
|
41
|
+
before running (triggers-027). That entry covers automatic workflows on Copilot PRs;
|
|
42
|
+
this entry covers the inability to manually dispatch TO a Copilot branch via the UI.
|
|
43
|
+
fix: |
|
|
44
|
+
There is no UI fix — Copilot agent branches cannot be selected in the GitHub Actions
|
|
45
|
+
web interface workflow_dispatch branch picker.
|
|
46
|
+
|
|
47
|
+
**Workaround: use the GitHub CLI to trigger workflow_dispatch with an explicit ref:**
|
|
48
|
+
```bash
|
|
49
|
+
gh workflow run <workflow-file.yml> --ref copilot/fix-issue-123
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Workaround: use the GitHub REST API:**
|
|
53
|
+
```bash
|
|
54
|
+
curl -X POST \
|
|
55
|
+
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
|
56
|
+
-H "Accept: application/vnd.github+json" \
|
|
57
|
+
https://api.github.com/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches \
|
|
58
|
+
-d '{"ref":"copilot/fix-issue-123","inputs":{}}'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Both the CLI and API accept any valid branch ref, including Copilot branches,
|
|
62
|
+
even though the UI does not display them.
|
|
63
|
+
fix_code:
|
|
64
|
+
- language: yaml
|
|
65
|
+
label: "Trigger workflow_dispatch on a Copilot branch via GitHub CLI (run from local terminal or another workflow)"
|
|
66
|
+
code: |
|
|
67
|
+
# Run locally or in a helper workflow step:
|
|
68
|
+
# gh workflow run deploy.yml --ref copilot/fix-issue-123
|
|
69
|
+
|
|
70
|
+
# Or call via the REST API in a workflow step:
|
|
71
|
+
- name: Dispatch workflow on Copilot branch
|
|
72
|
+
run: |
|
|
73
|
+
gh workflow run deploy.yml \
|
|
74
|
+
--ref "${{ github.head_ref }}" \
|
|
75
|
+
--repo ${{ github.repository }}
|
|
76
|
+
env:
|
|
77
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
78
|
+
prevention:
|
|
79
|
+
- "Use the GitHub CLI (gh workflow run --ref <branch>) instead of the UI when targeting Copilot branches"
|
|
80
|
+
- "Use the REST API dispatches endpoint with an explicit ref to trigger workflows on any branch"
|
|
81
|
+
- "Follow runner#4246 for updates on when UI support for Copilot branches may be added"
|
|
82
|
+
- "Build automation that runs in workflow_run triggered by the Copilot branch's push event instead of requiring manual dispatch"
|
|
83
|
+
docs:
|
|
84
|
+
- url: "https://github.com/actions/runner/issues/4246"
|
|
85
|
+
label: "runner#4246 — Cannot trigger workflows manually targeting a copilot agent branch (Feb 2026, 15 reactions)"
|
|
86
|
+
- url: "https://cli.github.com/manual/gh_workflow_run"
|
|
87
|
+
label: "GitHub CLI — gh workflow run (supports --ref for any branch)"
|
|
88
|
+
- url: "https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event"
|
|
89
|
+
label: "GitHub REST API — Create a workflow dispatch event (accepts any ref)"
|
|
90
|
+
- url: "https://docs.github.com/en/copilot/using-github-copilot/using-copilot-coding-agent-in-github"
|
|
91
|
+
label: "GitHub Docs — Using Copilot coding agent"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
id: runner-environment-246
|
|
2
|
+
title: 'Container `options:` empty string from matrix causes "The template is not valid. Unexpected value''" in runner >= 2.331.0'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- container
|
|
7
|
+
- matrix
|
|
8
|
+
- container-options
|
|
9
|
+
- runner-regression
|
|
10
|
+
- template-validation
|
|
11
|
+
- runner-2331
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'The template is not valid.*Unexpected value'
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'Unexpected value .{0,5}\.github/workflows'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Error: The template is not valid. .github/workflows/deploy.yml (Line: 42, Col: 15): Unexpected value ''"
|
|
19
|
+
- "Error: The template is not valid. .github/workflows/ci.yml (Line: 18, Col: 9): Unexpected value ''"
|
|
20
|
+
root_cause: |
|
|
21
|
+
In runner version 2.331.0, a regression was introduced in template validation for
|
|
22
|
+
container job `options:` fields. When a workflow uses a matrix to conditionally set
|
|
23
|
+
container options — so that some matrix combinations have no container options
|
|
24
|
+
(`options: ''` or `options: ${{ matrix.container_options }}` where the value is
|
|
25
|
+
unset/empty) — the runner's template evaluator now rejects the empty string with
|
|
26
|
+
"Unexpected value ''" and marks the job as failed before it even starts.
|
|
27
|
+
|
|
28
|
+
Prior to runner 2.331.0, an empty `options:` string was silently accepted and the
|
|
29
|
+
container job ran without extra Docker options. The 2.331.0 release tightened template
|
|
30
|
+
validation for the `container.options` field, rejecting any expression that evaluates
|
|
31
|
+
to an empty string at runtime.
|
|
32
|
+
|
|
33
|
+
**Common trigger pattern:**
|
|
34
|
+
```yaml
|
|
35
|
+
strategy:
|
|
36
|
+
matrix:
|
|
37
|
+
include:
|
|
38
|
+
- os: ubuntu
|
|
39
|
+
container_options: "--memory=2g"
|
|
40
|
+
- os: alpine
|
|
41
|
+
# No container_options key — evaluates to empty string
|
|
42
|
+
jobs:
|
|
43
|
+
build:
|
|
44
|
+
container:
|
|
45
|
+
image: myapp:latest
|
|
46
|
+
options: ${{ matrix.container_options }} # empty for alpine row
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This pattern worked on runner 2.330.0 but fails on 2.331.0+ with "Unexpected value ''".
|
|
50
|
+
The issue is tracked in actions/runner#4204 (open, labeled bug, Jan 2026).
|
|
51
|
+
fix: |
|
|
52
|
+
Add a fallback non-empty value to the options expression using the `||` operator.
|
|
53
|
+
A single space `' '` is enough to satisfy the validator while having no effect on
|
|
54
|
+
the container invocation, since Docker ignores empty/whitespace-only option strings.
|
|
55
|
+
|
|
56
|
+
Replace:
|
|
57
|
+
options: ${{ matrix.container_options }}
|
|
58
|
+
|
|
59
|
+
With:
|
|
60
|
+
options: ${{ matrix.container_options || ' ' }}
|
|
61
|
+
|
|
62
|
+
Alternatively, restructure the matrix to always provide a defined (non-empty)
|
|
63
|
+
`container_options` value, using a neutral default like `'--label=placeholder'`
|
|
64
|
+
for rows that don't need real options. This is more explicit and survives future
|
|
65
|
+
validator changes.
|
|
66
|
+
fix_code:
|
|
67
|
+
- language: yaml
|
|
68
|
+
label: 'Fallback to single space so template validator accepts an empty matrix value'
|
|
69
|
+
code: |
|
|
70
|
+
strategy:
|
|
71
|
+
matrix:
|
|
72
|
+
include:
|
|
73
|
+
- name: with-limits
|
|
74
|
+
container_options: "--memory=2g --cpus=1"
|
|
75
|
+
- name: no-limits
|
|
76
|
+
# container_options intentionally absent
|
|
77
|
+
|
|
78
|
+
jobs:
|
|
79
|
+
build:
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
container:
|
|
82
|
+
image: myapp:latest
|
|
83
|
+
# ✅ Use || ' ' to avoid empty-string rejection in runner >= 2.331.0
|
|
84
|
+
options: ${{ matrix.container_options || ' ' }}
|
|
85
|
+
steps:
|
|
86
|
+
- uses: actions/checkout@v4
|
|
87
|
+
- run: make build
|
|
88
|
+
- language: yaml
|
|
89
|
+
label: 'Explicit neutral default in the matrix row avoids the ambiguity entirely'
|
|
90
|
+
code: |
|
|
91
|
+
strategy:
|
|
92
|
+
matrix:
|
|
93
|
+
include:
|
|
94
|
+
- name: with-limits
|
|
95
|
+
container_options: "--memory=2g --cpus=1"
|
|
96
|
+
- name: no-limits
|
|
97
|
+
# ✅ Always set a value — Docker ignores a dummy label at no cost
|
|
98
|
+
container_options: "--label=no-extra-opts"
|
|
99
|
+
|
|
100
|
+
jobs:
|
|
101
|
+
build:
|
|
102
|
+
runs-on: ubuntu-latest
|
|
103
|
+
container:
|
|
104
|
+
image: myapp:latest
|
|
105
|
+
options: ${{ matrix.container_options }}
|
|
106
|
+
steps:
|
|
107
|
+
- uses: actions/checkout@v4
|
|
108
|
+
- run: make build
|
|
109
|
+
prevention:
|
|
110
|
+
- 'Never let `container.options` evaluate to an empty string — always provide a neutral fallback with `|| '' ''` or a dummy label'
|
|
111
|
+
- 'Test matrix workflows with every combination, including rows that skip optional matrix keys like `container_options`'
|
|
112
|
+
- 'Pin runner version in self-hosted setups to detect regressions before rolling out to all jobs'
|
|
113
|
+
- 'Track actions/runner#4204 for a permanent fix; apply the `|| '' ''` workaround in the interim'
|
|
114
|
+
docs:
|
|
115
|
+
- url: 'https://github.com/actions/runner/issues/4204'
|
|
116
|
+
label: 'actions/runner#4204: "The template is not valid" when container.options is not set in matrix (open, regression in 2.331.0)'
|
|
117
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-jobs-in-a-container'
|
|
118
|
+
label: 'GitHub Docs: Running jobs in a container — options field'
|
|
119
|
+
- url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idcontaineroptions'
|
|
120
|
+
label: 'GitHub Docs: jobs.<job_id>.container.options'
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
id: runner-environment-247
|
|
2
|
+
title: '`apt-get install` stalls ~75 seconds on ubuntu-24.04 — "Processing triggers for man-db" post-install hook'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: warning
|
|
5
|
+
tags:
|
|
6
|
+
- ubuntu-24.04
|
|
7
|
+
- apt-get
|
|
8
|
+
- man-db
|
|
9
|
+
- package-install
|
|
10
|
+
- performance
|
|
11
|
+
- slow
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Processing triggers for man-db \('
|
|
14
|
+
flags: 'i'
|
|
15
|
+
- regex: 'man-db.*stall|stall.*man-db'
|
|
16
|
+
flags: 'i'
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Processing triggers for man-db (2.12.0-4build2) ..."
|
|
19
|
+
- "Processing triggers for man-db (2.12.0-4build2) ..."
|
|
20
|
+
root_cause: |
|
|
21
|
+
On ubuntu-24.04 runners, installing any package that triggers the `man-db` post-install
|
|
22
|
+
hook causes a ~75-second stall during the `apt-get install` step.
|
|
23
|
+
|
|
24
|
+
The `man-db` daemon (which indexes manual pages for `man` lookups) has a post-install
|
|
25
|
+
trigger that runs `mandb` to rebuild the manual page database after packages are
|
|
26
|
+
installed. On ubuntu-24.04, this database rebuild operation runs synchronously and
|
|
27
|
+
takes approximately 60-90 seconds, blocking the apt post-install phase.
|
|
28
|
+
|
|
29
|
+
Any package that installs manual pages (cmake, make, build-essential, gcc, etc.)
|
|
30
|
+
triggers this slow hook. Workflows that install multiple packages in a single
|
|
31
|
+
`apt-get install` step will still only pay the cost once (one rebuild per transaction),
|
|
32
|
+
but even a single apt install with man pages will stall for over a minute.
|
|
33
|
+
|
|
34
|
+
The `man-db` package is installed by default on ubuntu-24.04 runner images. This
|
|
35
|
+
issue was reported in September 2025 and remains open as of June 2026 (runner#4030).
|
|
36
|
+
fix: |
|
|
37
|
+
Option 1 — Remove man-db before running apt-get install commands:
|
|
38
|
+
```yaml
|
|
39
|
+
- name: Remove man-db to prevent slow post-install trigger
|
|
40
|
+
run: sudo apt-get remove --purge -y man-db
|
|
41
|
+
```
|
|
42
|
+
After removing man-db, all subsequent apt installs skip the man page indexing trigger.
|
|
43
|
+
|
|
44
|
+
Option 2 — Disable the man-db auto-update file (lighter weight):
|
|
45
|
+
```yaml
|
|
46
|
+
- name: Disable man-db auto-update
|
|
47
|
+
run: sudo rm -f /var/lib/man-db/auto-update
|
|
48
|
+
```
|
|
49
|
+
This deletes the sentinel file that triggers auto-update, preventing the stall without
|
|
50
|
+
uninstalling man-db.
|
|
51
|
+
|
|
52
|
+
Option 3 — Set DEBIAN_FRONTEND and skip recommended packages (partial mitigation):
|
|
53
|
+
Some packages still trigger man-db via direct page installation, but combining
|
|
54
|
+
`--no-install-recommends` reduces the set of affected packages.
|
|
55
|
+
fix_code:
|
|
56
|
+
- language: yaml
|
|
57
|
+
label: "Remove man-db before apt-get installs to eliminate the stall"
|
|
58
|
+
code: |
|
|
59
|
+
- name: Remove man-db (prevents ~75s stall on ubuntu-24.04)
|
|
60
|
+
run: sudo apt-get remove --purge -y man-db
|
|
61
|
+
|
|
62
|
+
- name: Install dependencies
|
|
63
|
+
run: sudo apt-get install -y cmake make build-essential
|
|
64
|
+
prevention:
|
|
65
|
+
- "Remove or disable man-db at the start of jobs that run apt-get install on ubuntu-24.04"
|
|
66
|
+
- "Use sudo rm -f /var/lib/man-db/auto-update as a lighter-weight alternative to removing the package"
|
|
67
|
+
- "Combine all apt-get installs into a single command to pay the man-db trigger cost only once"
|
|
68
|
+
- "Monitor job timing — if an apt step takes 75+ extra seconds on ubuntu-24.04, man-db is the cause"
|
|
69
|
+
docs:
|
|
70
|
+
- url: "https://github.com/actions/runner/issues/4030"
|
|
71
|
+
label: "GitHub runner#4030 — man-db trigger severely stalls package installation on ubuntu-24.04"
|
|
72
|
+
- url: "https://manpages.ubuntu.com/manpages/noble/man8/mandb.8.html"
|
|
73
|
+
label: "Ubuntu mandb(8) manual — man page database indexer"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
id: runner-environment-248
|
|
2
|
+
title: '`actions/checkout` causes "Duplicate header: Authorization" — git returns 400 on subsequent git operations'
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- checkout
|
|
7
|
+
- git
|
|
8
|
+
- authorization
|
|
9
|
+
- http-extraheader
|
|
10
|
+
- credentials
|
|
11
|
+
- 400
|
|
12
|
+
- duplicate-header
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'Duplicate header.*Authorization|remote.*Duplicate header.*Authorization'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'fatal.*unable to access.*The requested URL returned error: 400'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
- regex: 'http\.extraheader.*AUTHORIZATION.*duplicate|duplicate.*AUTHORIZATION.*extraheader'
|
|
19
|
+
flags: 'i'
|
|
20
|
+
error_messages:
|
|
21
|
+
- "remote: Duplicate header: \"Authorization\""
|
|
22
|
+
- "fatal: unable to access 'https://github.com/org/repo/': The requested URL returned error: 400"
|
|
23
|
+
- "Error: The process '/usr/bin/git' failed with exit code 128"
|
|
24
|
+
root_cause: |
|
|
25
|
+
When `actions/checkout` runs, it configures git credentials by setting an
|
|
26
|
+
`http.extraheader` entry containing an `AUTHORIZATION: bearer <token>` header.
|
|
27
|
+
This header is added to git's global or repository-level config so that all
|
|
28
|
+
subsequent git operations over HTTPS are authenticated.
|
|
29
|
+
|
|
30
|
+
The "Duplicate header: Authorization" error occurs when a second Authorization
|
|
31
|
+
header is injected on top of the one already set by checkout. This can happen in
|
|
32
|
+
two scenarios:
|
|
33
|
+
|
|
34
|
+
1. **Pin to @main or an unstable tag**: Using `actions/checkout@main` (or any
|
|
35
|
+
edge/pre-release revision) picks up unreleased changes that may change the
|
|
36
|
+
credential injection mechanism. In some checkout versions, the http.extraheader
|
|
37
|
+
is written in a way that stacks with git's built-in credential manager, sending
|
|
38
|
+
two conflicting Authorization headers in the same HTTP request.
|
|
39
|
+
|
|
40
|
+
2. **Multiple checkout calls with persist-credentials: true** (the default):
|
|
41
|
+
The first checkout sets the global http.extraheader. A second checkout step
|
|
42
|
+
(e.g., checking out a second repository) may add a new header without first
|
|
43
|
+
removing the existing one, depending on the git version and checkout version.
|
|
44
|
+
|
|
45
|
+
GitHub's servers reject HTTP requests with two Authorization headers with HTTP 400,
|
|
46
|
+
returning "Duplicate header: Authorization" in the response. Git then reports
|
|
47
|
+
`fatal: unable to access ... The requested URL returned error: 400`.
|
|
48
|
+
|
|
49
|
+
The bug was reported in checkout#2299 (Nov 2025, 10 reactions) and checkout#2215
|
|
50
|
+
(Jul 2025, 8 reactions) and remained open as of June 2026.
|
|
51
|
+
fix: |
|
|
52
|
+
Option 1 — Pin to a stable released version tag instead of @main:
|
|
53
|
+
Replace `actions/checkout@main` with a specific release tag (e.g.,
|
|
54
|
+
`actions/checkout@v4` or `actions/checkout@v4.2.2`). The stable release series
|
|
55
|
+
has known credential injection behaviour that does not produce duplicate headers.
|
|
56
|
+
|
|
57
|
+
Option 2 — Clear http.extraheader between checkout steps:
|
|
58
|
+
If you must run multiple checkout steps, add a step between them to clear the
|
|
59
|
+
credential header before the second checkout:
|
|
60
|
+
```yaml
|
|
61
|
+
- name: Clear git credentials before second checkout
|
|
62
|
+
run: git config --global --unset-all http.extraheader || true
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Option 3 — Use token: input only on the checkout that needs it and set
|
|
66
|
+
persist-credentials: false on checkouts that do not need to push:
|
|
67
|
+
```yaml
|
|
68
|
+
- uses: actions/checkout@v4
|
|
69
|
+
with:
|
|
70
|
+
persist-credentials: false
|
|
71
|
+
```
|
|
72
|
+
fix_code:
|
|
73
|
+
- language: yaml
|
|
74
|
+
label: "Pin to stable checkout version to avoid duplicate header bug"
|
|
75
|
+
code: |
|
|
76
|
+
- name: Checkout repository
|
|
77
|
+
uses: actions/checkout@v4 # Pin to stable tag, NOT @main
|
|
78
|
+
with:
|
|
79
|
+
fetch-depth: 0
|
|
80
|
+
|
|
81
|
+
- language: yaml
|
|
82
|
+
label: "Clear git extraheader between multiple checkout steps"
|
|
83
|
+
code: |
|
|
84
|
+
- uses: actions/checkout@v4
|
|
85
|
+
with:
|
|
86
|
+
repository: org/first-repo
|
|
87
|
+
|
|
88
|
+
- name: Clear git credentials
|
|
89
|
+
run: git config --global --unset-all http.extraheader || true
|
|
90
|
+
|
|
91
|
+
- uses: actions/checkout@v4
|
|
92
|
+
with:
|
|
93
|
+
repository: org/second-repo
|
|
94
|
+
path: second-repo
|
|
95
|
+
prevention:
|
|
96
|
+
- "Never pin actions to @main or mutable tags — always use immutable version tags (e.g., @v4 or @v4.2.2)"
|
|
97
|
+
- "If using multiple checkout steps, set persist-credentials: false on steps that don't require pushing"
|
|
98
|
+
- "Check git config --global --list | grep extraheader if unexpected 400 errors occur on git operations"
|
|
99
|
+
- "Upgrade to the latest stable actions/checkout release when a new version is available"
|
|
100
|
+
docs:
|
|
101
|
+
- url: "https://github.com/actions/checkout/issues/2299"
|
|
102
|
+
label: "checkout#2299 — Duplicate header: Authorization (Nov 2025, 10 reactions)"
|
|
103
|
+
- url: "https://github.com/actions/checkout/issues/2215"
|
|
104
|
+
label: "checkout#2215 — actions/checkout@v4 fails with Duplicate header: Authorization, 400 (Jul 2025, 8 reactions)"
|
|
105
|
+
- url: "https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable"
|
|
106
|
+
label: "GitHub Actions security hardening — credential best practices"
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
id: silent-failures-122
|
|
2
|
+
title: '`fetch-tags: false` is silently ignored when `fetch-depth: 0` — all tags are still fetched'
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- checkout
|
|
7
|
+
- fetch-tags
|
|
8
|
+
- fetch-depth
|
|
9
|
+
- git
|
|
10
|
+
- tags
|
|
11
|
+
- shallow-clone
|
|
12
|
+
- option-conflict
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'fetch-tags.*false.*fetch-depth.*0|fetch-depth.*0.*fetch-tags.*false'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
- regex: 'tags.*still.*fetched|fetching.*tags.*despite.*false'
|
|
17
|
+
flags: 'i'
|
|
18
|
+
error_messages:
|
|
19
|
+
- "# No error message — tags are silently fetched despite fetch-tags: false"
|
|
20
|
+
root_cause: |
|
|
21
|
+
`actions/checkout` provides two related but conflicting inputs:
|
|
22
|
+
- `fetch-depth: 0` — fetches ALL commits and branches (unshallow clone); this
|
|
23
|
+
implicitly fetches ALL tags as well because full history requires resolving all
|
|
24
|
+
tag references
|
|
25
|
+
- `fetch-tags: false` — instructs the action NOT to fetch tags
|
|
26
|
+
|
|
27
|
+
When BOTH options are used together (`fetch-depth: 0` and `fetch-tags: false`),
|
|
28
|
+
`fetch-tags: false` is silently ignored. The full-history fetch that `fetch-depth: 0`
|
|
29
|
+
triggers uses a git fetch command that includes tag references, and the action does
|
|
30
|
+
not apply a `--no-tags` flag in this code path.
|
|
31
|
+
|
|
32
|
+
As a result, all repository tags end up in the local clone even though the workflow
|
|
33
|
+
explicitly requested `fetch-tags: false`. There is no error, warning, or annotation
|
|
34
|
+
— the tags are simply present.
|
|
35
|
+
|
|
36
|
+
This is a code-path gap in actions/checkout rather than a documented design decision.
|
|
37
|
+
The issue was reported in checkout#2195 (Jun 2025, 10 reactions) and remained open
|
|
38
|
+
as of June 2026.
|
|
39
|
+
|
|
40
|
+
Common situations where this matters:
|
|
41
|
+
- Workflows that use `git describe` and want to avoid picking up pre-release or
|
|
42
|
+
unrelated tags from the full history
|
|
43
|
+
- Versioning scripts that filter by tag presence to determine release status
|
|
44
|
+
- Workflows that check `git tag -l` to decide whether to create a new tag
|
|
45
|
+
fix: |
|
|
46
|
+
Since `fetch-tags: false` is not honoured with `fetch-depth: 0`, the workaround is
|
|
47
|
+
to explicitly delete the fetched tags in a step immediately after checkout:
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
- uses: actions/checkout@v4
|
|
51
|
+
with:
|
|
52
|
+
fetch-depth: 0
|
|
53
|
+
fetch-tags: false # Has no effect — tags are fetched anyway
|
|
54
|
+
|
|
55
|
+
- name: Remove all fetched tags (workaround for fetch-depth:0 + fetch-tags:false bug)
|
|
56
|
+
run: git tag -d $(git tag -l) || true
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Alternatively, if the goal is to avoid tag resolution during git operations, use
|
|
60
|
+
`--no-tags` in subsequent git fetch calls:
|
|
61
|
+
```yaml
|
|
62
|
+
- run: git fetch --no-tags origin
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If the full commit history is needed but not all tags, also consider filtering
|
|
66
|
+
the specific tags needed using `git fetch origin refs/tags/<specific-tag>:refs/tags/<specific-tag>`
|
|
67
|
+
after deleting the unwanted ones.
|
|
68
|
+
fix_code:
|
|
69
|
+
- language: yaml
|
|
70
|
+
label: "Delete all fetched tags after checkout when fetch-tags: false is needed with full history"
|
|
71
|
+
code: |
|
|
72
|
+
- uses: actions/checkout@v4
|
|
73
|
+
with:
|
|
74
|
+
fetch-depth: 0
|
|
75
|
+
fetch-tags: false # NOTE: currently ignored with fetch-depth: 0
|
|
76
|
+
|
|
77
|
+
- name: Delete all tags (workaround — fetch-tags:false ignored with fetch-depth:0)
|
|
78
|
+
run: |
|
|
79
|
+
TAGS=$(git tag -l)
|
|
80
|
+
if [ -n "$TAGS" ]; then
|
|
81
|
+
git tag -d $TAGS
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
- language: yaml
|
|
85
|
+
label: "Fetch full history via explicit git command with --no-tags as an alternative"
|
|
86
|
+
code: |
|
|
87
|
+
- uses: actions/checkout@v4
|
|
88
|
+
with:
|
|
89
|
+
fetch-depth: 1 # Shallow initial checkout
|
|
90
|
+
|
|
91
|
+
- name: Fetch full history without tags
|
|
92
|
+
run: git fetch --unshallow --no-tags
|
|
93
|
+
prevention:
|
|
94
|
+
- "Do not rely on fetch-tags: false to suppress tag fetching when fetch-depth: 0 is also set — it has no effect"
|
|
95
|
+
- "If tag presence affects workflow logic, explicitly verify the tag state with git tag -l after checkout"
|
|
96
|
+
- "Track checkout#2195 for a fix in future actions/checkout releases"
|
|
97
|
+
- "As a workaround, delete all tags after checkout using: git tag -d $(git tag -l) || true"
|
|
98
|
+
docs:
|
|
99
|
+
- url: "https://github.com/actions/checkout/issues/2195"
|
|
100
|
+
label: "checkout#2195 — fetch-tags: false still fetches tags if fetch-depth is 0 (Jun 2025, 10 reactions)"
|
|
101
|
+
- url: "https://github.com/actions/checkout#usage"
|
|
102
|
+
label: "actions/checkout Usage — fetch-depth and fetch-tags inputs"
|
package/package.json
CHANGED