@htekdev/actions-debugger 1.0.80 → 1.0.81

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.
@@ -0,0 +1,88 @@
1
+ id: caching-artifacts-048
2
+ title: "actions/cache lookup-only: true checks cache existence but does NOT restore files"
3
+ category: caching-artifacts
4
+ severity: silent-failure
5
+ tags:
6
+ - cache
7
+ - lookup-only
8
+ - cache-hit
9
+ - restore
10
+ - silent-no-op
11
+ patterns:
12
+ - regex: 'lookup-only.*true'
13
+ flags: 'i'
14
+ - regex: 'Cache found and can be restored from key'
15
+ flags: 'i'
16
+ error_messages:
17
+ - "Cache found and can be restored from key: node-modules-abc123"
18
+ - "lookup-only is set, skipping restore."
19
+ root_cause: |
20
+ The lookup-only: true input on actions/cache@v4 instructs the action to query
21
+ the cache service and report whether an entry exists (via the cache-hit output)
22
+ without transferring any data to the runner. No files are downloaded or written
23
+ to disk.
24
+
25
+ Developers who add lookup-only: true hoping to get a "lightweight restore that
26
+ skips the upload post-step" receive a misleading green step. The cache-hit output
27
+ reports 'true', the step log says "Cache found and can be restored from key",
28
+ and everything looks successful — but the working directory is empty.
29
+ Subsequent build steps that depend on the cached files fail with cryptic errors
30
+ (missing executables, import errors, or blank node_modules) that appear unrelated
31
+ to caching.
32
+
33
+ The intended use case for lookup-only is a two-job pipeline: Job A checks if the
34
+ cache exists (lookup-only: true) and, if not, builds and saves it; Job B restores
35
+ normally. It is NOT a performance shortcut for regular restore-and-save workflows.
36
+ fix: |
37
+ Remove lookup-only: true from any step that should actually restore cached files.
38
+ Use lookup-only: true only in a dedicated pre-check job that gates whether an
39
+ expensive build step needs to run, combined with a normal actions/cache step in
40
+ the subsequent job that performs the real restore.
41
+ fix_code:
42
+ - language: yaml
43
+ label: "Remove lookup-only for normal cache restore (most cases)"
44
+ code: |
45
+ - name: Restore node_modules cache
46
+ id: cache
47
+ uses: actions/cache@v4
48
+ with:
49
+ path: node_modules
50
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
51
+ restore-keys: ${{ runner.os }}-node-
52
+ # Do NOT set lookup-only: true here — files must be restored
53
+ - language: yaml
54
+ label: "Correct pattern: lookup-only in check job, real restore in build job"
55
+ code: |
56
+ jobs:
57
+ check-cache:
58
+ runs-on: ubuntu-latest
59
+ outputs:
60
+ cache-hit: ${{ steps.lookup.outputs.cache-hit }}
61
+ steps:
62
+ - id: lookup
63
+ uses: actions/cache@v4
64
+ with:
65
+ path: node_modules
66
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
67
+ lookup-only: true # only check — no download
68
+
69
+ build:
70
+ needs: check-cache
71
+ runs-on: ubuntu-latest
72
+ steps:
73
+ - uses: actions/cache@v4
74
+ with:
75
+ path: node_modules
76
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
77
+ # no lookup-only — restores files and saves after build
78
+ - if: needs.check-cache.outputs.cache-hit != 'true'
79
+ run: npm ci
80
+ prevention:
81
+ - "Only use lookup-only: true in a dedicated cache-check job that feeds a conditional build gate."
82
+ - "After using lookup-only, always follow up with a standard actions/cache step (no lookup-only) in the job that needs the files."
83
+ - "If you want to skip the post-step cache save, use actions/cache/restore instead of setting lookup-only: true."
84
+ docs:
85
+ - url: "https://github.com/actions/cache#inputs"
86
+ label: "actions/cache README: inputs including lookup-only"
87
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows"
88
+ label: "GitHub Actions: Caching dependencies"
@@ -0,0 +1,67 @@
1
+ id: caching-artifacts-047
2
+ title: "Re-run job fails with 'artifact already exists' when artifact name includes github.run_id"
3
+ category: caching-artifacts
4
+ severity: error
5
+ tags:
6
+ - upload-artifact
7
+ - re-run
8
+ - run_id
9
+ - run_attempt
10
+ - artifact-name
11
+ - collision
12
+ patterns:
13
+ - regex: 'An artifact with this name already exists on the workflow run'
14
+ flags: 'i'
15
+ - regex: 'Unable to upload artifact.*already exists'
16
+ flags: 'i'
17
+ error_messages:
18
+ - "An artifact with this name already exists on the workflow run."
19
+ - "Unable to upload artifact: An artifact with this name already exists on the run."
20
+ root_cause: |
21
+ github.run_id identifies a workflow run and remains the same value across all
22
+ re-run attempts triggered from the GitHub UI or REST API. When an artifact name
23
+ is constructed using github.run_id (e.g., my-build-${{ github.run_id }}), the
24
+ first attempt uploads successfully. If the job fails and is re-run, the re-run
25
+ attempt tries to create an artifact with the identical name on the same run.
26
+
27
+ actions/upload-artifact@v4 enforces unique artifact names per run as a hard
28
+ error (changed from v3 which silently overwrote). The re-run therefore fails
29
+ immediately with "artifact already exists" before any meaningful work is done.
30
+
31
+ Teams reach for run_id because it appears to be a unique-per-execution
32
+ identifier. It is not: it is unique per workflow trigger, stable across all
33
+ re-run attempts within that trigger.
34
+ fix: |
35
+ Append github.run_attempt to the artifact name. run_attempt starts at 1 and
36
+ increments for each re-run of the same run_id, making the combined value unique
37
+ across attempts. Consumers of the artifact (download steps, workflow_run
38
+ triggers) must include run_attempt in their own references to locate the
39
+ correct artifact.
40
+ fix_code:
41
+ - language: yaml
42
+ label: "Include run_attempt to make artifact name unique across re-runs"
43
+ code: |
44
+ - name: Upload build artifact
45
+ uses: actions/upload-artifact@v4
46
+ with:
47
+ # run_attempt increments on each re-run, preventing name collision
48
+ name: my-build-${{ github.run_id }}-${{ github.run_attempt }}
49
+ path: dist/
50
+ - language: yaml
51
+ label: "Download the matching artifact in a downstream job"
52
+ code: |
53
+ - name: Download build artifact
54
+ uses: actions/download-artifact@v4
55
+ with:
56
+ name: my-build-${{ github.run_id }}-${{ github.run_attempt }}
57
+ path: dist/
58
+ prevention:
59
+ - "Never use github.run_id alone as an artifact name uniqueness guarantee — it is stable across all re-runs of the same run."
60
+ - "Combine github.run_id with github.run_attempt to produce a name that is unique within a run AND across re-run attempts."
61
+ - "Use a matrix value or job name as the differentiator instead of run_id when the artifact is consumed by a job in the same workflow."
62
+ - "Check the GitHub UI Actions > Artifacts list to inspect existing artifact names before debugging re-run failures."
63
+ docs:
64
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/storing-workflow-data-as-artifacts"
65
+ label: "GitHub Actions: Storing workflow data as artifacts"
66
+ - url: "https://github.com/actions/upload-artifact#not-uploading-to-the-same-artifact"
67
+ label: "actions/upload-artifact: unique artifact names per run"
@@ -0,0 +1,96 @@
1
+ id: silent-failures-075
2
+ title: "if: failure() notification job is skipped when workflow is manually cancelled"
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - if-condition
7
+ - failure
8
+ - cancelled
9
+ - always
10
+ - notification
11
+ - cleanup
12
+ patterns:
13
+ - regex: 'if:\s*failure\(\)'
14
+ flags: 'i'
15
+ - regex: 'Skipping.*cancelled'
16
+ flags: 'i'
17
+ error_messages:
18
+ - "Skipping: The job was skipped because one or more of its needs jobs was cancelled."
19
+ root_cause: |
20
+ When a workflow is manually cancelled (via the GitHub UI "Cancel run" button or
21
+ the REST API), in-progress jobs receive a cancellation signal. Jobs that have not
22
+ yet started are given the conclusion "cancelled". Jobs with if: failure() are
23
+ only evaluated against the "failure" conclusion — "cancelled" is a distinct
24
+ conclusion value. Because cancelled != failure, the condition evaluates to false
25
+ and the notification or cleanup job is silently skipped.
26
+
27
+ This catches teams by surprise when they add an alerting job intended to fire
28
+ whenever CI does not succeed:
29
+
30
+ notify-on-failure:
31
+ needs: build
32
+ if: failure()
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - name: Send Slack alert
36
+ ...
37
+
38
+ When an operator cancels a run mid-flight, the build job is marked cancelled,
39
+ not failed. The notify job evaluates if: failure() → false and is skipped with
40
+ no Slack message. The on-call team receives no signal that a run was interrupted.
41
+
42
+ The failure() function returns true only when at least one needed job has a
43
+ "failure" conclusion. It does NOT return true for "cancelled" conclusions.
44
+ fix: |
45
+ Use if: failure() || cancelled() to catch both failure and cancellation, or use
46
+ if: always() combined with a conditional check on the job's outcome inside the
47
+ step if you only want to act on non-success outcomes.
48
+ fix_code:
49
+ - language: yaml
50
+ label: "Catch both failure and cancellation in a notification job"
51
+ code: |
52
+ jobs:
53
+ build:
54
+ runs-on: ubuntu-latest
55
+ steps:
56
+ - run: make build
57
+
58
+ notify-on-failure:
59
+ needs: build
60
+ # failure() catches job failures; cancelled() catches manual cancellation
61
+ if: failure() || cancelled()
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - name: Send failure or cancellation alert
65
+ run: |
66
+ echo "Build concluded: ${{ needs.build.result }}"
67
+ # Send Slack/PagerDuty notification here
68
+ - language: yaml
69
+ label: "Use always() and gate on outcome inside the step for fine-grained control"
70
+ code: |
71
+ jobs:
72
+ build:
73
+ runs-on: ubuntu-latest
74
+ steps:
75
+ - run: make build
76
+
77
+ notify:
78
+ needs: build
79
+ if: always()
80
+ runs-on: ubuntu-latest
81
+ steps:
82
+ - name: Notify only on non-success outcomes
83
+ if: needs.build.result != 'success'
84
+ run: |
85
+ echo "Build result: ${{ needs.build.result }}"
86
+ # Handles: failure, cancelled, skipped
87
+ prevention:
88
+ - "Use if: failure() || cancelled() instead of if: failure() for any alert, notification, or cleanup job that must run whenever CI does not succeed."
89
+ - "Prefer if: always() when the downstream job should run regardless of upstream outcome (e.g., always upload test reports)."
90
+ - "Audit all notification and cleanup jobs after cancellation to confirm they fired as expected — cancelled runs don't generate failure alerts by default."
91
+ - "The cancelled() function returns true when the workflow or job has been cancelled; combine with failure() using || for comprehensive coverage."
92
+ docs:
93
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-conditions-to-control-job-execution"
94
+ label: "GitHub Actions: Using conditions to control job execution"
95
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-conditions-to-control-job-execution#available-status-check-functions"
96
+ label: "GitHub Actions: Available status check functions — failure(), cancelled(), always()"
@@ -0,0 +1,98 @@
1
+ id: triggers-054
2
+ title: "workflow_dispatch choice input not validated when triggered via REST API — invalid values silently accepted"
3
+ category: triggers
4
+ severity: silent-failure
5
+ tags:
6
+ - workflow-dispatch
7
+ - choice-input
8
+ - rest-api
9
+ - inputs
10
+ - validation
11
+ - gh-cli
12
+ patterns:
13
+ - regex: 'type:\s*choice'
14
+ flags: 'i'
15
+ - regex: 'inputs\.\w+.*choice'
16
+ flags: 'i'
17
+ error_messages:
18
+ - "Workflow ran with input value not in declared choices list"
19
+ root_cause: |
20
+ The type: choice input in on.workflow_dispatch.inputs renders a dropdown
21
+ selector in the GitHub Actions UI and prevents users from submitting values
22
+ outside the declared options list. This validation is enforced entirely
23
+ client-side in the browser.
24
+
25
+ When a workflow is triggered via the GitHub REST API
26
+ (POST /repos/{owner}/{repo}/actions/workflows/{id}/dispatches) or the GitHub
27
+ CLI (gh workflow run --field key=value), GitHub does NOT validate that the
28
+ supplied value matches any of the declared choices. Any arbitrary string is
29
+ accepted and forwarded as-is to the workflow.
30
+
31
+ A workflow dispatched with inputs: { environment: "PROD" } when the declared
32
+ choices are ["prod", "staging", "dev"] will run with inputs.environment == "PROD".
33
+ Every if: condition that compares against the lower-case variants evaluates to
34
+ false, all deployment branches are skipped, and the workflow exits successfully
35
+ with no indication that the value was invalid. The mistyped run is invisible
36
+ unless logs are inspected manually.
37
+ fix: |
38
+ Add an explicit allowlist validation step at the top of the workflow that checks
39
+ the input value and exits with a non-zero status and a descriptive error message
40
+ if the value is not in the expected set. This makes API-triggered runs fail fast
41
+ with a clear error rather than silently no-oping.
42
+ fix_code:
43
+ - language: yaml
44
+ label: "Validate choice input with an allowlist check"
45
+ code: |
46
+ on:
47
+ workflow_dispatch:
48
+ inputs:
49
+ environment:
50
+ type: choice
51
+ description: Target deployment environment
52
+ options:
53
+ - prod
54
+ - staging
55
+ - dev
56
+
57
+ jobs:
58
+ deploy:
59
+ runs-on: ubuntu-latest
60
+ steps:
61
+ - name: Validate environment input
62
+ run: |
63
+ allowed="prod staging dev"
64
+ input="${{ inputs.environment }}"
65
+ if [[ ! " $allowed " =~ " ${input} " ]]; then
66
+ echo "::error::Invalid environment '${input}'. Allowed values: $allowed"
67
+ exit 1
68
+ fi
69
+
70
+ - name: Deploy to ${{ inputs.environment }}
71
+ run: echo "Deploying to ${{ inputs.environment }}"
72
+ - language: yaml
73
+ label: "Case-insensitive validation with normalisation"
74
+ code: |
75
+ - name: Validate and normalise environment input
76
+ id: validate
77
+ run: |
78
+ input=$(echo "${{ inputs.environment }}" | tr '[:upper:]' '[:lower:]')
79
+ allowed=("prod" "staging" "dev")
80
+ valid=false
81
+ for v in "${allowed[@]}"; do
82
+ [[ "$input" == "$v" ]] && valid=true && break
83
+ done
84
+ if [[ "$valid" != "true" ]]; then
85
+ echo "::error::Unrecognised environment '${{ inputs.environment }}' (normalised: '$input'). Allowed: ${allowed[*]}"
86
+ exit 1
87
+ fi
88
+ echo "environment=$input" >> "$GITHUB_OUTPUT"
89
+ prevention:
90
+ - "Treat type: choice as a UI hint only — always validate inputs programmatically inside the workflow."
91
+ - "Add an allowlist guard as the first step of any job that branches on a choice input."
92
+ - "Document valid values and casing rules in the input description field so API callers know the expected format."
93
+ - "Use actionlint to detect misspelled choices at authoring time before they reach production."
94
+ docs:
95
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_dispatch"
96
+ label: "GitHub Actions: workflow_dispatch event — inputs"
97
+ - url: "https://docs.github.com/en/rest/actions/workflows#create-a-workflow-dispatch-event"
98
+ label: "GitHub REST API: Create a workflow dispatch event"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@htekdev/actions-debugger",
3
- "version": "1.0.80",
3
+ "version": "1.0.81",
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",