@htekdev/actions-debugger 1.0.123 → 1.0.125

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.
Files changed (32) hide show
  1. package/errors/caching-artifacts/caching-artifacts-073.yml +100 -0
  2. package/errors/caching-artifacts/caching-artifacts-074.yml +117 -0
  3. package/errors/known-unsolved/known-unsolved-070.yml +83 -0
  4. package/errors/known-unsolved/known-unsolved-071.yml +122 -0
  5. package/errors/known-unsolved/known-unsolved-072.yml +143 -0
  6. package/errors/permissions-auth/permissions-auth-071.yml +144 -0
  7. package/errors/permissions-auth/permissions-auth-072.yml +112 -0
  8. package/errors/permissions-auth/permissions-auth-073.yml +127 -0
  9. package/errors/permissions-auth/permissions-auth-074.yml +106 -0
  10. package/errors/permissions-auth/permissions-auth-075.yml +137 -0
  11. package/errors/runner-environment/runner-environment-224.yml +74 -0
  12. package/errors/runner-environment/runner-environment-225.yml +85 -0
  13. package/errors/runner-environment/runner-environment-226.yml +91 -0
  14. package/errors/runner-environment/runner-environment-227.yml +106 -0
  15. package/errors/runner-environment/runner-environment-228.yml +117 -0
  16. package/errors/runner-environment/runner-environment-229.yml +119 -0
  17. package/errors/runner-environment/runner-environment-230.yml +129 -0
  18. package/errors/runner-environment/runner-environment-231.yml +90 -0
  19. package/errors/runner-environment/runner-environment-232.yml +131 -0
  20. package/errors/runner-environment/runner-environment-233.yml +90 -0
  21. package/errors/runner-environment/runner-environment-234.yml +114 -0
  22. package/errors/runner-environment/runner-environment-235.yml +151 -0
  23. package/errors/silent-failures/silent-failures-112.yml +97 -0
  24. package/errors/silent-failures/silent-failures-113.yml +110 -0
  25. package/errors/silent-failures/silent-failures-114.yml +116 -0
  26. package/errors/silent-failures/silent-failures-115.yml +130 -0
  27. package/errors/silent-failures/silent-failures-116.yml +117 -0
  28. package/errors/silent-failures/silent-failures-117.yml +137 -0
  29. package/errors/silent-failures/silent-failures-118.yml +156 -0
  30. package/errors/yaml-syntax/yaml-syntax-075.yml +128 -0
  31. package/errors/yaml-syntax/yaml-syntax-076.yml +107 -0
  32. package/package.json +1 -1
@@ -0,0 +1,117 @@
1
+ id: silent-failures-116
2
+ title: 'actions/checkout sparse-checkout silently falls back to full REST API download when git < 2.28'
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - checkout
7
+ - sparse-checkout
8
+ - git-version
9
+ - self-hosted
10
+ - rest-api-fallback
11
+ - silent
12
+ - git
13
+ patterns:
14
+ - regex: 'Minimum Git version required for sparse checkout is 2\.28'
15
+ flags: 'i'
16
+ - regex: 'Failed to initialize CommandManager.*sparse'
17
+ flags: 'i'
18
+ - regex: 'Falling back to downloading using the GitHub REST API'
19
+ flags: 'i'
20
+ error_messages:
21
+ - 'Minimum Git version required for sparse checkout is 2.28. Your git (/path/to/git) is 2.23'
22
+ - 'Falling back to downloading using the GitHub REST API'
23
+ - 'Warning: The git version installed on this runner is 2.x which is lower than the minimum required version 2.28 for sparse checkout'
24
+ root_cause: |
25
+ actions/checkout supports `sparse-checkout` configuration, which requires git 2.28 or
26
+ later (because sparse-checkout with cone-mode patterns uses `git sparse-checkout init`,
27
+ introduced in that version).
28
+
29
+ When checkout attempts to initialise the git CommandManager and git is present on the
30
+ runner but too old to satisfy the sparse-checkout requirement, the action's
31
+ `getGitCommandManager` function throws an error. However, this error is caught in a
32
+ broad try/catch block:
33
+
34
+ } catch (err) {
35
+ // Git is required for LFS
36
+ if (settings.lfs) { throw err }
37
+ // otherwise: silently continue without git
38
+ }
39
+
40
+ Because `lfs` is not enabled, the error is swallowed without being logged to the
41
+ workflow output. Checkout falls back to downloading the repository as a ZIP archive
42
+ via the GitHub REST API and extracts it into the workspace.
43
+
44
+ The result is a **full clone** of the repository (subject to `fetch-depth`), not a
45
+ sparse checkout. All files are present in the workspace instead of only the requested
46
+ sparse paths. Workflows relying on sparse checkout to reduce disk usage or build time
47
+ proceed with the full repository content, potentially causing subtle downstream failures
48
+ (out-of-disk, dependency version mismatches, unexpected files in build artefacts) with
49
+ no indication that sparse-checkout was skipped.
50
+
51
+ This primarily affects **self-hosted runners** where a locally installed git binary is
52
+ older than 2.28 (e.g., git 2.17 shipped with Ubuntu 18.04, or git 2.23 on Amazon Linux
53
+ running in an older container). GitHub-hosted runners ship git 2.43+ and are not affected.
54
+
55
+ Reported in actions/checkout#2435 (May 2026).
56
+ fix: |
57
+ **Step 1: Identify the failure.**
58
+ Add a step to print the git version before checkout:
59
+ - run: git --version
60
+
61
+ If the version is below 2.28, that is the root cause of the silent fallback.
62
+
63
+ **Step 2: Upgrade git on self-hosted runners.**
64
+ Install a modern git version (2.28+) on the runner. For Ubuntu:
65
+ sudo add-apt-repository ppa:git-core/ppa
66
+ sudo apt-get update && sudo apt-get install -y git
67
+
68
+ **Step 3: Until git is upgraded, disable sparse-checkout.**
69
+ Remove or guard the `sparse-checkout` input to avoid the silent fallback:
70
+ sparse-checkout: ''
71
+
72
+ **Alternative: require a minimum git version in the workflow.**
73
+ Fail fast with an explicit error rather than a silent fallback by checking the version
74
+ before the checkout step.
75
+ fix_code:
76
+ - language: yaml
77
+ label: 'Add explicit git version gate before checkout with sparse-checkout'
78
+ code: |
79
+ - name: Verify git version supports sparse-checkout
80
+ run: |
81
+ GIT_VERSION=$(git --version | awk '{print $3}')
82
+ MIN_VERSION="2.28.0"
83
+ if [ "$(printf '%s\n' "$MIN_VERSION" "$GIT_VERSION" | sort -V | head -n1)" != "$MIN_VERSION" ]; then
84
+ echo "::error::git $GIT_VERSION is too old for sparse-checkout (requires >= 2.28)"
85
+ exit 1
86
+ fi
87
+
88
+ - uses: actions/checkout@v6
89
+ with:
90
+ sparse-checkout: |
91
+ src/
92
+ tests/
93
+ - language: yaml
94
+ label: 'Upgrade git on Ubuntu self-hosted runner before checkout'
95
+ code: |
96
+ - name: Ensure git >= 2.28 for sparse-checkout support
97
+ run: |
98
+ sudo add-apt-repository -y ppa:git-core/ppa
99
+ sudo apt-get update -q
100
+ sudo apt-get install -y git
101
+ git --version
102
+
103
+ - uses: actions/checkout@v6
104
+ with:
105
+ sparse-checkout: |
106
+ src/
107
+ prevention:
108
+ - 'Always confirm the git version on self-hosted runners meets the minimum requirements for checkout features you use; add a preflight check step if runners may have older git installs'
109
+ - 'Pin to a specific version of actions/checkout and audit the changelog when upgrading — the sparse-checkout silent fallback has existed across multiple major versions'
110
+ - 'If disk usage is a concern and sparse-checkout is required, add a post-checkout assertion that only the expected sparse paths exist, which will catch the REST API fallback'
111
+ docs:
112
+ - url: 'https://github.com/actions/checkout/issues/2435'
113
+ label: 'actions/checkout#2435 — Errors from initializing gitCommandManager are silently ignored'
114
+ - url: 'https://github.com/actions/checkout#usage'
115
+ label: 'actions/checkout README — sparse-checkout input and requirements'
116
+ - url: 'https://git-scm.com/docs/git-sparse-checkout'
117
+ label: 'Git documentation — git-sparse-checkout (requires git 2.25+, cone mode 2.26+)'
@@ -0,0 +1,137 @@
1
+ id: silent-failures-117
2
+ title: 'if: always() runs steps even when the workflow is manually cancelled — use success() || failure() to respect cancellation'
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - if-condition
7
+ - always
8
+ - cancelled
9
+ - cleanup
10
+ - notification
11
+ - status-check
12
+ - silent-failure
13
+ patterns:
14
+ - regex: 'if:\s*always\(\)'
15
+ flags: 'i'
16
+ - regex: 'if:\s*\$\{\{\s*always\(\)\s*\}\}'
17
+ flags: 'i'
18
+ error_messages:
19
+ - '(no error emitted — steps run unexpectedly during workflow cancellation)'
20
+ root_cause: |
21
+ Developers commonly add `if: always()` to steps or jobs that should run even when
22
+ a previous step fails — for example, uploading test reports after a failed test run,
23
+ or sending failure notifications.
24
+
25
+ The silent failure is that `always()` evaluates to `true` in ALL cases including
26
+ when the workflow is manually cancelled. When a user or a concurrency group cancellation
27
+ triggers, any step or job with `if: always()` still executes. This is often unintended:
28
+ the developer expected "run on failure" not "run even when someone cancels".
29
+
30
+ GitHub Actions documentation explicitly warns about this:
31
+ "Consider using if: !cancelled() if you want the step or job to continue
32
+ executing when the current run is cancelled. [...] Using always() is not
33
+ recommended as it causes a step to run when a workflow is cancelled, which
34
+ can result in unintended behavior."
35
+
36
+ Common scenarios where this causes problems:
37
+ - Test report upload step runs when a PR author cancels a run, unnecessarily consuming
38
+ runner minutes and potentially posting partial results to a PR check.
39
+ - Slack/PagerDuty notification fires for a manual cancellation, producing false alerts.
40
+ - Deploy rollback job runs during a cancellation of a non-deployment workflow, triggering
41
+ rollback unexpectedly.
42
+ - Cleanup job executes with only partial data because the workflow was cut short.
43
+
44
+ The correct alternative for "run when success OR failure, but NOT when cancelled" is:
45
+ if: success() || failure()
46
+ or equivalently:
47
+ if: ${{ !cancelled() }}
48
+
49
+ Source: Stack Overflow q/58858429 (415 upvotes, 327k views), GitHub Docs always() warning,
50
+ test-summary/action#51 community discussion (Jan 2025).
51
+ fix: |
52
+ Replace `if: always()` with `if: success() || failure()` for steps and jobs that
53
+ should run on success or failure but NOT when the workflow is cancelled.
54
+
55
+ Use `if: always()` only when you explicitly want the step to run even during
56
+ cancellation — for example, an emergency teardown that must run regardless.
57
+
58
+ Three options depending on intent:
59
+
60
+ 1. Run on success or failure (skip on cancel): `if: success() || failure()`
61
+ 2. Run on any non-success outcome (skip on cancel): `if: failure()`
62
+ 3. Run always including on cancel (current behaviour): `if: always()`
63
+
64
+ GitHub also documents `if: ${{ !cancelled() }}` as equivalent to option 1 in
65
+ most contexts.
66
+ fix_code:
67
+ - language: yaml
68
+ label: 'Before: if: always() fires even on manual cancellation'
69
+ code: |
70
+ jobs:
71
+ test:
72
+ runs-on: ubuntu-latest
73
+ steps:
74
+ - run: pytest --junitxml=results.xml
75
+
76
+ - name: Upload test results
77
+ if: always() # fires even when the job is cancelled mid-run
78
+ uses: actions/upload-artifact@v4
79
+ with:
80
+ name: test-results
81
+ path: results.xml
82
+ - language: yaml
83
+ label: 'After: if: success() || failure() respects cancellation'
84
+ code: |
85
+ jobs:
86
+ test:
87
+ runs-on: ubuntu-latest
88
+ steps:
89
+ - run: pytest --junitxml=results.xml
90
+
91
+ - name: Upload test results
92
+ if: success() || failure() # runs on pass or fail; skips on cancel
93
+ uses: actions/upload-artifact@v4
94
+ with:
95
+ name: test-results
96
+ path: results.xml
97
+ - language: yaml
98
+ label: 'Notification job: skip Slack alert on cancellation'
99
+ code: |
100
+ jobs:
101
+ build:
102
+ runs-on: ubuntu-latest
103
+ steps:
104
+ - run: make build
105
+
106
+ notify:
107
+ needs: build
108
+ # success() || failure() fires on build success or failure, not on cancel
109
+ if: success() || failure()
110
+ runs-on: ubuntu-latest
111
+ steps:
112
+ - name: Notify Slack
113
+ run: |
114
+ echo "Build result: ${{ needs.build.result }}"
115
+ # Sends alert only on success or failure — not on manual cancel
116
+
117
+ # Only use always() when you MUST run on cancellation too:
118
+ emergency-cleanup:
119
+ needs: build
120
+ if: always() # intentional: must clean up even if cancelled
121
+ runs-on: ubuntu-latest
122
+ timeout-minutes: 4 # stay under the 5-min forced-kill window
123
+ steps:
124
+ - run: ./cleanup.sh
125
+ prevention:
126
+ - 'Default to `if: success() || failure()` for upload, notification, and cleanup steps — reserve `if: always()` only for teardown steps that truly must run on cancellation'
127
+ - 'Remember: `always()` is not "run when failure"; it is "run even when cancelled" — the distinction matters for notification jobs'
128
+ - 'GitHub docs recommend `if: ${{ !cancelled() }}` as an alternative to `if: success() || failure()` for the same effect'
129
+ - 'Combine `if: always()` with `timeout-minutes:` on any job that runs after cancellation; GitHub force-kills all jobs after 5 minutes of the cancellation signal'
130
+ - 'Run a quick cancellation test: manually cancel the workflow and verify whether notification steps fire unexpectedly'
131
+ docs:
132
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#always'
133
+ label: 'GitHub Docs: always() status check function — cancellation warning'
134
+ - url: 'https://stackoverflow.com/questions/58858429/how-to-run-a-github-actions-step-even-if-the-previous-step-fails-while-still-failing-the-job'
135
+ label: 'Stack Overflow 58858429: Run step on failure while still failing the job (415 votes)'
136
+ - url: 'https://github.com/test-summary/action/issues/51'
137
+ label: 'test-summary/action#51: Use success() || failure() instead of always() to prevent running on cancel'
@@ -0,0 +1,156 @@
1
+ id: silent-failures-118
2
+ title: 'Reusable workflow jobs.<name>.result is always empty string in on.workflow_call.outputs value'
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - reusable-workflow
7
+ - workflow_call
8
+ - outputs
9
+ - jobs-result
10
+ - empty-string
11
+ - silent-failure
12
+ - job-outcome
13
+ patterns:
14
+ - regex: 'value:\s*\$\{\{\s*jobs\.[a-zA-Z0-9_-]+\.result\s*\}\}'
15
+ flags: 'i'
16
+ - regex: 'needs\.[a-zA-Z0-9_-]+\.outputs\.[a-zA-Z0-9_-]+.*result'
17
+ flags: 'i'
18
+ error_messages:
19
+ - '(no error emitted — needs.<job>.outputs.<name> resolves to empty string in the caller)'
20
+ root_cause: |
21
+ GitHub Actions documentation states that the `jobs` context is available in
22
+ reusable workflows and includes `jobs.<job_id>.result` — the job conclusion value
23
+ ("success", "failure", "cancelled", or "skipped"). Developers use this to expose job
24
+ outcomes to calling workflows via `on.workflow_call.outputs`.
25
+
26
+ However, `jobs.<job_id>.result` does NOT work when used in the `value:` expression of
27
+ `on.workflow_call.outputs`. It always resolves to an empty string ("") in the caller's
28
+ `needs.<called_job>.outputs.<name>`.
29
+
30
+ This is distinct from the two-level output declaration issue (sf-060) where developers
31
+ forget to declare outputs at the workflow_call level. Here the developer correctly
32
+ declares both levels, but uses `jobs.<name>.result` (the job outcome) instead of
33
+ `jobs.<name>.outputs.<step-output>` (a custom output). The `.result` property is
34
+ simply not available via this mechanism.
35
+
36
+ Workarounds discovered by the community:
37
+ 1. Explicitly capture the outcome as a step output and then chain it through job outputs:
38
+ steps:
39
+ - id: capture
40
+ run: echo "result=${{ job.status }}" >> "$GITHUB_OUTPUT"
41
+ outputs:
42
+ result: ${{ steps.capture.outputs.result }}
43
+ 2. Use `fromJSON(toJSON(jobs.build)).result` — this works because it forces the
44
+ expression evaluator to serialise and deserialise the jobs context object, which
45
+ happens to populate the result at evaluation time. This is an accidental workaround
46
+ and not guaranteed to remain stable.
47
+
48
+ The root cause is that the `jobs.<job_id>.result` property in the workflow_call outputs
49
+ `value:` expression is evaluated before job conclusions are fully committed to the
50
+ expression context, unlike `jobs.<job_id>.outputs.<name>` which goes through a different
51
+ code path.
52
+
53
+ Source: actions/runner#2495 (open since 2023, multiple upvotes),
54
+ actions/runner#3087 (Cannot access jobs.<id>.result from on.workflow_call.outputs..value).
55
+ fix: |
56
+ Do NOT use `value: ${{ jobs.<name>.result }}` in on.workflow_call.outputs — it will
57
+ always be empty in the caller.
58
+
59
+ **Option 1 (recommended): Capture outcome via a step output.**
60
+ Add a final step that writes `job.status` to GITHUB_OUTPUT, then chain it up:
61
+ step output → job output → workflow_call output
62
+
63
+ **Option 2: Use fromJSON(toJSON(jobs.<name>)).result workaround.**
64
+ The fromJSON/toJSON wrapper forces the jobs context to be serialised at evaluation
65
+ time, which makes .result available. This is a workaround — prefer option 1.
66
+
67
+ **Option 3: Control caller behavior via job success/failure instead of reading result.**
68
+ If you only need to run downstream caller jobs conditionally based on the reusable
69
+ workflow succeeding or failing, use `if: needs.<job>.result == 'success'` — the
70
+ `needs.<job>.result` in the CALLER is always correct and does NOT need to go through
71
+ workflow_call outputs.
72
+ fix_code:
73
+ - language: yaml
74
+ label: 'Broken: value: ${{ jobs.build.result }} is always empty in caller'
75
+ code: |
76
+ # .github/workflows/reusable.yml — BROKEN pattern
77
+ on:
78
+ workflow_call:
79
+ outputs:
80
+ build-result:
81
+ description: 'Job outcome of the build job'
82
+ value: ${{ jobs.build.result }} # always empty string in caller
83
+
84
+ jobs:
85
+ build:
86
+ runs-on: ubuntu-latest
87
+ steps:
88
+ - run: make build
89
+ - language: yaml
90
+ label: 'Fixed: capture outcome via step output and chain it through job outputs'
91
+ code: |
92
+ # .github/workflows/reusable.yml — CORRECT pattern
93
+ on:
94
+ workflow_call:
95
+ outputs:
96
+ build-result:
97
+ description: 'Job outcome of the build job'
98
+ value: ${{ jobs.build.outputs.result }} # works correctly
99
+
100
+ jobs:
101
+ build:
102
+ runs-on: ubuntu-latest
103
+ outputs:
104
+ result: ${{ steps.capture.outputs.result }}
105
+ steps:
106
+ - run: make build
107
+
108
+ - id: capture
109
+ if: always()
110
+ shell: bash
111
+ run: echo "result=${{ job.status }}" >> "$GITHUB_OUTPUT"
112
+ - language: yaml
113
+ label: 'Alternative workaround: fromJSON(toJSON(jobs.build)).result (fragile, prefer option 1)'
114
+ code: |
115
+ # .github/workflows/reusable.yml — accidental workaround (not recommended)
116
+ on:
117
+ workflow_call:
118
+ outputs:
119
+ build-result:
120
+ value: ${{ fromJSON(toJSON(jobs.build)).result }}
121
+
122
+ jobs:
123
+ build:
124
+ runs-on: ubuntu-latest
125
+ steps:
126
+ - run: make build
127
+ - language: yaml
128
+ label: 'Caller: reading needs.<job>.result directly works and does NOT need workflow_call outputs'
129
+ code: |
130
+ # .github/workflows/caller.yml
131
+ # needs.<job>.result in the CALLER is always correct — no need to pass it through outputs
132
+ jobs:
133
+ call-build:
134
+ uses: ./.github/workflows/reusable.yml
135
+
136
+ deploy:
137
+ needs: call-build
138
+ # This works correctly — reads the reusable workflow conclusion directly
139
+ if: needs.call-build.result == 'success'
140
+ runs-on: ubuntu-latest
141
+ steps:
142
+ - run: ./deploy.sh
143
+ prevention:
144
+ - 'Never use `value: ${{ jobs.<name>.result }}` in workflow_call outputs — capture it as a step output first'
145
+ - 'If you only need to gate downstream caller jobs on reusable workflow success/failure, use `needs.<job>.result` in the caller directly — it works without going through workflow_call outputs'
146
+ - 'Test output values by printing them in the caller with `run: echo "${{ needs.<job>.outputs.<name> }}"` before relying on them in logic'
147
+ - 'The `fromJSON(toJSON(...)).result` workaround works today but is fragile — prefer the step-output capture pattern'
148
+ docs:
149
+ - url: 'https://github.com/actions/runner/issues/2495'
150
+ label: 'actions/runner#2495: Reusable workflow does not output individual job result'
151
+ - url: 'https://github.com/actions/runner/issues/3087'
152
+ label: 'actions/runner#3087: Cannot access jobs.<id>.result from on.workflow_call.outputs..value'
153
+ - url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onworkflow_calloutputs'
154
+ label: 'GitHub Docs: on.workflow_call.outputs syntax reference'
155
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#jobs-context'
156
+ label: 'GitHub Docs: jobs context (available only in reusable workflows)'
@@ -0,0 +1,128 @@
1
+ id: yaml-syntax-075
2
+ title: 'environment: shorthand string format silently rejects needs.* and jobs.* contexts — use environment.name form'
3
+ category: yaml-syntax
4
+ severity: error
5
+ tags:
6
+ - environment
7
+ - dynamic-environment
8
+ - needs-context
9
+ - expression
10
+ - shorthand
11
+ - template-validation
12
+ patterns:
13
+ - regex: 'Unrecognized named-value.*needs.*environment'
14
+ flags: 'i'
15
+ - regex: "Unrecognized named-value: 'needs'.*position.*expression.*needs\\."
16
+ flags: 'i'
17
+ - regex: 'TemplateValidationException.*Unrecognized named-value.*needs'
18
+ flags: 'i'
19
+ error_messages:
20
+ - "The workflow is not valid. .github/workflows/deploy.yml (Line: 15, Col: 18): Unrecognized named-value: 'needs'. Located at position 1 within expression: needs.set_environment.outputs.my_env"
21
+ - "Unrecognized named-value: 'needs'. Located at position 1 within expression: needs.X.outputs.env"
22
+ root_cause: |
23
+ The GitHub Actions workflow parser supports two forms for specifying a job environment:
24
+
25
+ 1. Shorthand string: `environment: production` or `environment: ${{ some.expression }}`
26
+ 2. Explicit mapping: `environment:\n name: ${{ expression }}\n url: ${{ expression }}`
27
+
28
+ The shorthand string form (`environment: ${{ ... }}`) supports only a limited expression
29
+ context and does NOT support the `needs.*` or `jobs.*` contexts, even when the job
30
+ has `needs:` declared. Attempting to use `needs.X.outputs.Y` inside the shorthand
31
+ produces a TemplateValidationException at workflow parse time:
32
+
33
+ "Unrecognized named-value: 'needs'. Located at position 1 within expression: needs.X.outputs.Y"
34
+
35
+ The explicit mapping form (`environment:\n name: ${{ ... }}`) DOES support the `needs.*`
36
+ context fully — this is the documented way to set a dynamic environment name or URL.
37
+
38
+ This trips up developers who:
39
+ - Copy the shorthand form from documentation examples and add an expression.
40
+ - Want to choose between staging/production environments based on the branch output
41
+ of an earlier job.
42
+ - Try to set a dynamic environment URL using `needs.deploy.outputs.url`.
43
+
44
+ The error appears only at workflow validation time (not at job runtime), which means
45
+ the entire workflow is rejected before any jobs run.
46
+ fix: |
47
+ Use the explicit `environment:` mapping format with `name:` and optionally `url:` sub-keys
48
+ instead of the shorthand string with a `${{ }}` expression.
49
+
50
+ The shorthand `environment: ${{ expression }}` is only for literal strings or simple
51
+ expressions without `needs.*` / `jobs.*`. Once you need cross-job outputs, switch to
52
+ the explicit form.
53
+ fix_code:
54
+ - language: yaml
55
+ label: 'Broken — shorthand with needs.* context causes TemplateValidationException'
56
+ code: |
57
+ jobs:
58
+ determine-env:
59
+ runs-on: ubuntu-latest
60
+ outputs:
61
+ target: ${{ steps.pick.outputs.env }}
62
+ steps:
63
+ - id: pick
64
+ run: echo "env=production" >> $GITHUB_OUTPUT
65
+
66
+ deploy:
67
+ needs: determine-env
68
+ runs-on: ubuntu-latest
69
+ # ❌ BROKEN: shorthand does not support needs.* context
70
+ environment: ${{ needs.determine-env.outputs.target }}
71
+ steps:
72
+ - run: echo "deploying"
73
+
74
+ - language: yaml
75
+ label: 'Fixed — explicit environment.name form supports needs.* context'
76
+ code: |
77
+ jobs:
78
+ determine-env:
79
+ runs-on: ubuntu-latest
80
+ outputs:
81
+ target: ${{ steps.pick.outputs.env }}
82
+ steps:
83
+ - id: pick
84
+ run: |
85
+ if [[ "${{ github.ref_name }}" == "main" ]]; then
86
+ echo "env=production" >> $GITHUB_OUTPUT
87
+ else
88
+ echo "env=staging" >> $GITHUB_OUTPUT
89
+ fi
90
+
91
+ deploy:
92
+ needs: determine-env
93
+ runs-on: ubuntu-latest
94
+ # ✅ FIXED: explicit name: sub-key supports needs.* context
95
+ environment:
96
+ name: ${{ needs.determine-env.outputs.target }}
97
+ url: ${{ needs.deploy.outputs.deploy_url }}
98
+ steps:
99
+ - run: echo "deploying to ${{ needs.determine-env.outputs.target }}"
100
+
101
+ - language: yaml
102
+ label: 'Dynamic environment from workflow_dispatch input — correct form'
103
+ code: |
104
+ on:
105
+ workflow_dispatch:
106
+ inputs:
107
+ environment:
108
+ required: true
109
+ type: environment
110
+ jobs:
111
+ deploy:
112
+ runs-on: ubuntu-latest
113
+ # ✅ Use explicit name: when referencing inputs.* or needs.* contexts
114
+ environment:
115
+ name: ${{ inputs.environment }}
116
+ steps:
117
+ - run: echo "deploying to ${{ inputs.environment }}"
118
+ prevention:
119
+ - 'Always use the `environment:\n name: ${{ }}` form (never the shorthand string) when the environment name is dynamic or derived from another job.'
120
+ - 'Remember: `environment: ${{ expression }}` is valid syntax but only for literal strings — it silently ignores `needs.*` context at parse time, producing a hard error.'
121
+ - 'Lint with actionlint (v1.7.0+) which detects this pattern before pushing.'
122
+ docs:
123
+ - url: 'https://github.com/actions/runner/issues/998'
124
+ label: 'actions/runner#998 — Setting job environment dynamically fails with needs context in shorthand form'
125
+ - url: 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idenvironment'
126
+ label: 'GitHub Docs — jobs.<job_id>.environment syntax reference'
127
+ - url: 'https://stackoverflow.com/questions/65826284/use-dynamic-input-value-for-environment-in-github-actions-workflow-job'
128
+ label: 'Stack Overflow — Dynamic environment in GitHub Actions workflow job (many upvotes)'
@@ -0,0 +1,107 @@
1
+ id: yaml-syntax-076
2
+ title: 'actionlint <=1.7.7 rejects valid `entrypoint` and `command` keys on service containers — schema lag after April 2026 GitHub changelog'
3
+ category: yaml-syntax
4
+ severity: error
5
+ tags:
6
+ - actionlint
7
+ - service-container
8
+ - entrypoint
9
+ - command
10
+ - schema-lag
11
+ - linting
12
+ - CI-check
13
+ patterns:
14
+ - regex: 'unexpected key "entrypoint" for "services" section'
15
+ flags: 'i'
16
+ - regex: 'unexpected key "command" for "services" section'
17
+ flags: 'i'
18
+ - regex: 'unexpected key "(?:entrypoint|command)" for "services" section\. expected one of "credentials", "env", "image", "options", "ports", "volumes"'
19
+ flags: 'i'
20
+ error_messages:
21
+ - '.github/workflows/test.yaml:9:9: unexpected key "entrypoint" for "services" section. expected one of "credentials", "env", "image", "options", "ports", "volumes" [syntax-check]'
22
+ - '.github/workflows/test.yaml:10:9: unexpected key "command" for "services" section. expected one of "credentials", "env", "image", "options", "ports", "volumes" [syntax-check]'
23
+ root_cause: |
24
+ GitHub Actions added support for `entrypoint` and `command` keys on service container
25
+ definitions on April 2, 2026 (GitHub Actions: Early April 2026 updates changelog). These
26
+ new keys allow overriding the service container's default entrypoint and command directly from
27
+ workflow YAML, matching Docker Compose semantics:
28
+
29
+ ```yaml
30
+ services:
31
+ redis:
32
+ image: redis
33
+ entrypoint: redis-server
34
+ command: --save 60 1 --loglevel warning
35
+ ```
36
+
37
+ actionlint versions <=1.7.7 validate service container keys against a hardcoded schema that
38
+ only permits `credentials`, `env`, `image`, `options`, `ports`, and `volumes`. When a workflow
39
+ uses the new `entrypoint` or `command` keys, actionlint reports them as unknown properties with
40
+ a `[syntax-check]` error.
41
+
42
+ This causes CI pipelines that run actionlint as a lint check to fail even though the workflow
43
+ is perfectly valid on GitHub.com. The fix was merged into actionlint on April 19, 2026 and
44
+ released in v1.7.8.
45
+
46
+ This is a schema-lag pattern: a new GitHub Actions feature was released before the static
47
+ analysis tool's schema was updated to recognize it, causing a false-positive lint failure.
48
+ fix: |
49
+ **Primary fix: Upgrade actionlint to >=1.7.8.**
50
+ The fix was merged April 19, 2026 (rhysd/actionlint#644). Update your CI pipeline to use
51
+ actionlint >=1.7.8:
52
+
53
+ ```yaml
54
+ - name: Run actionlint
55
+ uses: raven-actions/actionlint@v2
56
+ with:
57
+ version: latest # or pin to 1.7.8+
58
+ ```
59
+
60
+ **Temporary workaround (if upgrading is not immediately possible):** Add an inline ignore
61
+ comment to suppress the false-positive while on the older actionlint version:
62
+
63
+ ```yaml
64
+ services:
65
+ redis:
66
+ image: redis
67
+ entrypoint: redis-server # actionlint:ignore
68
+ command: --save 60 1 # actionlint:ignore
69
+ ```
70
+ fix_code:
71
+ - language: yaml
72
+ label: 'Upgrade actionlint in CI to >=1.7.8 to recognize entrypoint/command service keys'
73
+ code: |
74
+ - name: Run actionlint
75
+ uses: raven-actions/actionlint@v2
76
+ with:
77
+ version: 1.7.8 # Minimum version that supports entrypoint/command on service containers
78
+ - language: yaml
79
+ label: 'Valid service container workflow using new entrypoint/command keys (GitHub Actions only)'
80
+ code: |
81
+ jobs:
82
+ test:
83
+ runs-on: ubuntu-latest
84
+ services:
85
+ redis:
86
+ image: redis:7
87
+ # These keys are valid on GitHub Actions (GA April 2, 2026)
88
+ # but require actionlint >=1.7.8 for linting to pass
89
+ entrypoint: redis-server
90
+ command: --save 60 1 --loglevel warning
91
+ ports:
92
+ - 6379:6379
93
+ steps:
94
+ - uses: actions/checkout@v4
95
+ - name: Run tests
96
+ run: npm test
97
+ prevention:
98
+ - 'Pin actionlint to a specific version in CI and include it in your Dependabot or Renovate configuration so schema updates are picked up promptly when new GitHub Actions features are released.'
99
+ - 'When a new GitHub Actions feature ships, check the actionlint changelog (https://github.com/rhysd/actionlint/releases) for schema support before using the feature in linted workflows.'
100
+ - 'Use `# actionlint:ignore` comment as a short-term workaround for valid-but-unknown-to-linter keys while waiting for the schema update.'
101
+ docs:
102
+ - url: 'https://github.com/rhysd/actionlint/issues/644'
103
+ label: 'rhysd/actionlint#644 — Add support for entrypoint and command for service containers (fixed in v1.7.8)'
104
+ - url: 'https://github.blog/changelog/2026-04-02-github-actions-early-april-2026-updates/'
105
+ label: 'GitHub Changelog — April 2026 updates: entrypoint and command overrides for service containers'
106
+ - url: 'https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idservicesservice_identrypoint'
107
+ label: 'GitHub Docs — Workflow syntax: jobs.<job_id>.services.<service_id>.entrypoint'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@htekdev/actions-debugger",
3
- "version": "1.0.123",
3
+ "version": "1.0.125",
4
4
  "description": "65+ real GitHub Actions errors, queryable by agents. CLI + MCP server + Copilot skills + error database.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",