@htekdev/actions-debugger 1.0.12 → 1.0.13

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,83 @@
1
+ id: caching-artifacts-014
2
+ title: "actions/cache Exclusion Globs (! patterns) Silently Ignored"
3
+ category: caching-artifacts
4
+ severity: silent-failure
5
+ tags:
6
+ - cache
7
+ - glob
8
+ - exclusion
9
+ - negation
10
+ - silent-failure
11
+ patterns:
12
+ - regex: "path:(?:[^#\\n]*\\n)+\\s*!\\S"
13
+ flags: "im"
14
+ - regex: "path:.*\\n.*!~/"
15
+ flags: "im"
16
+ error_messages:
17
+ - "Cache saved with key"
18
+ - "Cache hit occurred on the primary key"
19
+ root_cause: |
20
+ The `actions/cache` action uses `@actions/glob` with `implicitDescendants: false`
21
+ to resolve the `path:` input into a file list for `tar`. With this setting, each
22
+ pattern is matched only at the top level — subdirectory enumeration does not happen.
23
+ As a result, negation patterns like `!~/cache/temp` never find anything to exclude
24
+ because there are no implicitly enumerated descendants to match against.
25
+
26
+ The cache save and restore operations complete successfully with no error or warning.
27
+ The "excluded" directory is silently included in the archive, leading to:
28
+ - Larger caches than intended, consuming quota faster
29
+ - Sensitive or temporary files accidentally persisted across runs
30
+ - False-positive cache key hits (excluded dir content changes don't invalidate cache)
31
+
32
+ This is a long-standing known limitation of `actions/cache` that has been reported
33
+ since v1 and remains unfixed in v4.
34
+ fix: |
35
+ `actions/cache` does not support exclusion globs. Restructure directories so the
36
+ content you want to cache lives in a dedicated subdirectory, and reference only that
37
+ path. If directory restructuring is not feasible, use a `run` step to create a
38
+ filtered `tar` archive before caching.
39
+ fix_code:
40
+ - language: yaml
41
+ label: "BROKEN — exclusion pattern silently does nothing"
42
+ code: |
43
+ - uses: actions/cache@v4
44
+ with:
45
+ path: |
46
+ ~/cache
47
+ !~/cache/temp # Silently ignored — temp/ is still cached
48
+ key: ${{ runner.os }}-cache-${{ hashFiles('**/package-lock.json') }}
49
+ - language: yaml
50
+ label: "CORRECT — restructure: cache only the subdirectory you need"
51
+ code: |
52
+ # Put persistent cache content in ~/cache/persistent/
53
+ # Put temp files in ~/cache/temp/ (not cached)
54
+ - uses: actions/cache@v4
55
+ with:
56
+ path: ~/cache/persistent # Cache only the isolated subdirectory
57
+ key: ${{ runner.os }}-cache-${{ hashFiles('**/package-lock.json') }}
58
+ - language: yaml
59
+ label: "CORRECT — manual tar with --exclude for complex cases"
60
+ code: |
61
+ - name: Save cache with exclusion
62
+ run: |
63
+ tar --exclude='$HOME/cache/temp' \
64
+ --exclude='$HOME/cache/.git' \
65
+ -czf /tmp/my-cache.tar.gz \
66
+ ~/cache
67
+
68
+ - uses: actions/cache@v4
69
+ with:
70
+ path: /tmp/my-cache.tar.gz
71
+ key: ${{ runner.os }}-filtered-${{ hashFiles('**/package-lock.json') }}
72
+ prevention:
73
+ - "Never rely on `!` negation patterns in `actions/cache` `path:` — they are silently ignored."
74
+ - "Audit cache paths to confirm no sensitive, secret, or large temporary files are included."
75
+ - "Isolate cacheable build artifacts into dedicated subdirectories at project setup time."
76
+ - "Set `enableCrossOsArchive: false` (default) and test on each OS independently."
77
+ docs:
78
+ - url: "https://github.com/actions/toolkit/issues/713"
79
+ label: "actions/toolkit#713 — Cache: excluding files or folders with ! not working"
80
+ - url: "https://github.com/actions/cache/blob/main/tips-and-workarounds.md"
81
+ label: "actions/cache — Tips and workarounds"
82
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows"
83
+ label: "Caching dependencies to speed up workflows"
@@ -0,0 +1,114 @@
1
+ id: known-unsolved-013
2
+ title: "GITHUB_ENV Set in Composite Action Not Updated on Repeated Calls in Same Job"
3
+ category: known-unsolved
4
+ severity: silent-failure
5
+ tags:
6
+ - composite-action
7
+ - GITHUB_ENV
8
+ - env-var
9
+ - repeated-calls
10
+ - known-limitation
11
+ patterns:
12
+ - regex: "echo.*>>\\s*\\$GITHUB_ENV"
13
+ flags: "i"
14
+ - regex: "GITHUB_ENV.*composite"
15
+ flags: "i"
16
+ error_messages:
17
+ - "environment variable not updated on second call"
18
+ - "variable retains value from first call"
19
+ root_cause: |
20
+ When a composite action sets an environment variable by appending to $GITHUB_ENV,
21
+ that variable is propagated to the parent job context after the composite action
22
+ completes. On the first call this works correctly. However, if the same composite
23
+ action is called a second time in the same job, the GITHUB_ENV write succeeds
24
+ internally but the parent job environment variable retains the value from the
25
+ first call.
26
+
27
+ This is a known platform limitation: GITHUB_ENV writes from within composite
28
+ actions are processed at step-boundary transitions. On repeated calls to the
29
+ same composite action in the same job, the runner does not re-apply the env
30
+ file changes correctly for variables already set — the first written value
31
+ persists for the job duration.
32
+
33
+ The limitation affects parameterized composite actions used as reusable units
34
+ that produce environment state, especially when called in loops, matrices, or
35
+ sequential steps with different inputs.
36
+
37
+ GitHub has not fixed this behavior; the recommended migration path is to use
38
+ GITHUB_OUTPUT instead of GITHUB_ENV for composite action outputs.
39
+ fix: |
40
+ Replace $GITHUB_ENV writes with $GITHUB_OUTPUT writes inside composite actions.
41
+ Step outputs are properly scoped per-invocation and do not have the repeated-call
42
+ limitation. Read the output in the calling workflow using steps.<id>.outputs.<name>.
43
+
44
+ If environment variables are strictly required at the job level, set them in the
45
+ calling workflow from the composite action outputs, not inside the composite action.
46
+ fix_code:
47
+ - language: yaml
48
+ label: "BROKEN composite action — GITHUB_ENV silently not updated on 2nd call"
49
+ code: |
50
+ # my-action/action.yml
51
+ inputs:
52
+ label:
53
+ required: true
54
+ runs:
55
+ using: composite
56
+ steps:
57
+ - run: echo "MY_LABEL=${{ inputs.label }}" >> $GITHUB_ENV
58
+ shell: bash
59
+ # Calling workflow:
60
+ # steps:
61
+ # - uses: ./my-action
62
+ # with: { label: "first" } # MY_LABEL = "first" ✓
63
+ # - uses: ./my-action
64
+ # with: { label: "second" } # MY_LABEL still "first" — silent bug!
65
+ - language: yaml
66
+ label: "CORRECT composite action — use GITHUB_OUTPUT"
67
+ code: |
68
+ # my-action/action.yml
69
+ inputs:
70
+ label:
71
+ required: true
72
+ outputs:
73
+ label:
74
+ description: "The resolved label"
75
+ value: ${{ steps.set.outputs.label }}
76
+ runs:
77
+ using: composite
78
+ steps:
79
+ - id: set
80
+ run: echo "label=${{ inputs.label }}" >> $GITHUB_OUTPUT
81
+ shell: bash
82
+ - language: yaml
83
+ label: "CORRECT calling workflow — propagate to env if needed"
84
+ code: |
85
+ steps:
86
+ - id: first
87
+ uses: ./my-action
88
+ with:
89
+ label: build-${{ github.sha }}
90
+
91
+ - id: second
92
+ uses: ./my-action
93
+ with:
94
+ label: deploy-${{ github.sha }}
95
+
96
+ - name: Use outputs from both calls
97
+ env:
98
+ FIRST_LABEL: ${{ steps.first.outputs.label }}
99
+ SECOND_LABEL: ${{ steps.second.outputs.label }}
100
+ run: |
101
+ echo "First: $FIRST_LABEL"
102
+ echo "Second: $SECOND_LABEL"
103
+ prevention:
104
+ - "Never use $GITHUB_ENV in composite actions that may be called more than once per job."
105
+ - "Migrate all composite action side effects from GITHUB_ENV to GITHUB_OUTPUT."
106
+ - "Test composite actions by calling them twice in the same job to catch this bug early."
107
+ - "Prefer outputs over env-var side effects for cleaner and predictable reuse semantics."
108
+ docs:
109
+ - url: "https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#outputs-for-composite-actions"
110
+ label: "Outputs for composite actions"
111
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-an-environment-variable"
112
+ label: "Workflow commands — setting an environment variable"
113
+ - url: "https://github.com/actions/runner/issues/789"
114
+ label: "actions/runner#789 — GITHUB_ENV does not update when running composite actions multiple times"
@@ -0,0 +1,90 @@
1
+ id: silent-failures-016
2
+ title: "failure() Returns false After continue-on-error Step"
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - continue-on-error
7
+ - failure
8
+ - status-functions
9
+ - error-handler
10
+ - silent-failure
11
+ patterns:
12
+ - regex: "continue-on-error:\\s*true"
13
+ flags: "i"
14
+ - regex: "if:\\s*(\\$\\{\\{\\s*)?failure\\(\\)"
15
+ flags: "i"
16
+ error_messages:
17
+ - "Skipping step: due to a failed condition"
18
+ root_cause: |
19
+ When a step has `continue-on-error: true` and that step fails, GitHub Actions
20
+ records the step outcome as failure but immediately converts the job aggregate
21
+ status back to success before evaluating the next step. This means that
22
+ failure() in a subsequent step's if condition evaluates to false — the runner
23
+ sees the job as currently-successful, not currently-failed.
24
+
25
+ The continue-on-error flag was designed to allow a job to proceed past a failing
26
+ step, but the side effect is that it consumes the failure signal from all
27
+ status-check functions. Developers commonly write error-handler steps using
28
+ if: failure(), expecting them to trigger when the prior step fails, but this
29
+ pattern silently breaks when continue-on-error: true is present on that prior step.
30
+
31
+ The step individual outcome context (steps.<id>.outcome) is the only reliable
32
+ way to detect the specific step result after continue-on-error.
33
+ fix: |
34
+ Replace if: failure() with if: steps.<step_id>.outcome == 'failure' when
35
+ writing error handlers for specific steps that use continue-on-error: true.
36
+ The steps.<id>.outcome context holds the actual step result regardless of
37
+ job-level status modifications.
38
+ fix_code:
39
+ - language: yaml
40
+ label: "WRONG — failure() never triggers after continue-on-error"
41
+ code: |
42
+ steps:
43
+ - name: Risky operation
44
+ id: risky
45
+ continue-on-error: true
46
+ run: ./might-fail.sh
47
+
48
+ - name: Handle failure
49
+ if: failure() # NEVER RUNS — job status was reset to success
50
+ run: ./notify-on-failure.sh
51
+ - language: yaml
52
+ label: "CORRECT — check specific step outcome"
53
+ code: |
54
+ steps:
55
+ - name: Risky operation
56
+ id: risky
57
+ continue-on-error: true
58
+ run: ./might-fail.sh
59
+
60
+ - name: Handle failure
61
+ if: steps.risky.outcome == 'failure'
62
+ run: ./notify-on-failure.sh
63
+
64
+ - name: Continue regardless
65
+ run: echo "Job continues either way"
66
+ - language: yaml
67
+ label: "CORRECT — combine with always() for comprehensive guards"
68
+ code: |
69
+ steps:
70
+ - name: Risky operation
71
+ id: risky
72
+ continue-on-error: true
73
+ run: ./might-fail.sh
74
+
75
+ - name: Handle step failure
76
+ if: always() && steps.risky.outcome == 'failure'
77
+ run: |
78
+ echo "::warning::Risky operation failed"
79
+ echo "## Failed" >> $GITHUB_STEP_SUMMARY
80
+ prevention:
81
+ - "Never use if: failure() as handler for a step with continue-on-error: true — use steps.<id>.outcome."
82
+ - "Reserve failure() for handlers that should trigger on any upstream job failure."
83
+ - "Add step IDs to all continue-on-error steps so outcome checking is always possible."
84
+ docs:
85
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-conditions-to-control-job-execution"
86
+ label: "Using conditions to control job execution"
87
+ - url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error"
88
+ label: "continue-on-error workflow syntax"
89
+ - url: "https://github.com/actions/toolkit/issues/1034"
90
+ label: "actions/toolkit#1034 — Wrong behaviour combining continue-on-error and failure()"
@@ -0,0 +1,71 @@
1
+ id: silent-failures-015
2
+ title: "Mixing ${{ }} Interpolation Inside if: Condition Causes Step to Always Run"
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - if-condition
7
+ - expression
8
+ - interpolation
9
+ - always-runs
10
+ - silent-failure
11
+ patterns:
12
+ - regex: "if:.*==.*'[^']*\\$\\{\\{[^}]+\\}\\}[^']*'"
13
+ flags: "i"
14
+ - regex: "if:.*'[^']*\\$\\{\\{[^}]+\\}\\}[^']*'.*=="
15
+ flags: "i"
16
+ error_messages:
17
+ - "step runs unexpectedly even when condition should be false"
18
+ - "if condition always evaluates true"
19
+ root_cause: |
20
+ When the `if:` field contains BOTH bare comparison operators AND a `${{ }}`
21
+ interpolation fragment within a quoted string (e.g.,
22
+ `if: github.ref == 'refs/heads/${{ env.BRANCH }}'`), GitHub Actions wraps the
23
+ entire bare-string value in `${{ ... }}` for expression evaluation. This makes
24
+ the outer expression evaluate the literal string
25
+ `"github.ref == 'refs/heads/main'"` — a non-empty string — which is always truthy.
26
+
27
+ The runner does not parse the embedded `${{ }}` fragment as an inner expression;
28
+ it only processes the outer auto-wrap. The result is that the step runs on every
29
+ trigger regardless of the actual comparison result — a textbook silent failure
30
+ since the workflow file looks syntactically correct and no error is emitted.
31
+ fix: |
32
+ Wrap the ENTIRE `if:` condition in `${{ }}` so the runner treats it as an
33
+ expression from the start. Use the `format()` function or direct context
34
+ comparisons instead of embedding `${{ }}` inside quoted strings.
35
+ fix_code:
36
+ - language: yaml
37
+ label: "WRONG — mixed interpolation, step always runs"
38
+ code: |
39
+ env:
40
+ TARGET_BRANCH: main
41
+ steps:
42
+ - name: Deploy to production
43
+ # This ALWAYS runs — the string is truthy regardless of branch
44
+ if: github.ref == 'refs/heads/${{ env.TARGET_BRANCH }}'
45
+ run: ./deploy.sh
46
+ - language: yaml
47
+ label: "CORRECT — wrap entire condition in ${{ }}"
48
+ code: |
49
+ env:
50
+ TARGET_BRANCH: main
51
+ steps:
52
+ - name: Deploy to production
53
+ if: ${{ github.ref == format('refs/heads/{0}', env.TARGET_BRANCH) }}
54
+ run: ./deploy.sh
55
+ - language: yaml
56
+ label: "CORRECT — direct string literal comparison"
57
+ code: |
58
+ steps:
59
+ - name: Deploy to production
60
+ if: ${{ github.ref == 'refs/heads/main' }}
61
+ run: ./deploy.sh
62
+ prevention:
63
+ - "Never embed `${{ }}` fragments inside quoted strings within `if:` — always wrap the entire condition."
64
+ - "Lint workflows with `actionlint` — it flags mixed expression/literal patterns in `if:` fields."
65
+ - "Prefer `format()` or direct context references over string interpolation in conditionals."
66
+ - "Test conditions on branches that should NOT match to verify they don't run."
67
+ docs:
68
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions"
69
+ label: "Evaluate expressions in workflows and actions"
70
+ - url: "https://github.com/actions/runner/issues/1173"
71
+ label: "actions/runner#1173 — If condition always evaluated as true when containing ${{ }} inside"
@@ -0,0 +1,100 @@
1
+ id: yaml-syntax-018
2
+ title: "Job if: Condition Skipped When Needed Job Is Skipped — Requires always() &&"
3
+ category: yaml-syntax
4
+ severity: error
5
+ tags:
6
+ - needs
7
+ - skipped
8
+ - if-condition
9
+ - always
10
+ - job-level
11
+ patterns:
12
+ - regex: "needs.*result.*skipped"
13
+ flags: "i"
14
+ - regex: "if:.*needs\\.[a-z_-]+\\.outputs"
15
+ flags: "i"
16
+ error_messages:
17
+ - "Skipping job: due to job needs not meeting conditions"
18
+ - "Job was skipped"
19
+ root_cause: |
20
+ When a job declares `needs: [job_a, job_b]` and any of those upstream jobs is in
21
+ a `skipped` state, GitHub Actions propagates the skip to the dependent job by
22
+ default — even when that job has its own `if:` condition that evaluates to `true`.
23
+
24
+ GitHub Actions imposes an implicit rule: all `needs` jobs must have
25
+ `result == 'success'` unless the dependent job's condition explicitly overrides
26
+ the check. A skipped upstream job does NOT satisfy the implicit success requirement.
27
+ The `if:` condition is NOT evaluated as a full override — it is only evaluated
28
+ AFTER the implicit needs-success check passes, so if that check fails the `if:`
29
+ is never reached.
30
+
31
+ The fix is `always() &&` at the start of the `if:` condition, which bypasses the
32
+ implicit needs-success requirement and forces the runner to evaluate the full condition.
33
+ fix: |
34
+ Prepend `always() &&` to any job-level `if:` where an upstream `needs` job may be
35
+ legitimately skipped. Alternatively, explicitly allow skipped results:
36
+ `needs.job_name.result == 'success' || needs.job_name.result == 'skipped'`.
37
+ fix_code:
38
+ - language: yaml
39
+ label: "WRONG — deploy skipped even though if evaluates true"
40
+ code: |
41
+ jobs:
42
+ build:
43
+ runs-on: ubuntu-latest
44
+ outputs:
45
+ should_deploy: ${{ steps.check.outputs.deploy }}
46
+ steps:
47
+ - id: check
48
+ run: echo "deploy=true" >> $GITHUB_OUTPUT
49
+
50
+ integration-tests:
51
+ needs: build
52
+ if: ${{ needs.build.outputs.should_deploy == 'false' }}
53
+ runs-on: ubuntu-latest
54
+ steps:
55
+ - run: echo "optional integration tests"
56
+
57
+ deploy:
58
+ needs: [build, integration-tests]
59
+ # PROBLEM: integration-tests was skipped → deploy is also skipped
60
+ # even though the condition below is true
61
+ if: ${{ needs.build.outputs.should_deploy == 'true' }}
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - run: ./deploy.sh
65
+ - language: yaml
66
+ label: "CORRECT — prepend always() to force evaluation"
67
+ code: |
68
+ jobs:
69
+ deploy:
70
+ needs: [build, integration-tests]
71
+ if: always() && needs.build.outputs.should_deploy == 'true'
72
+ runs-on: ubuntu-latest
73
+ steps:
74
+ - run: ./deploy.sh
75
+ - language: yaml
76
+ label: "CORRECT — explicit skipped result check"
77
+ code: |
78
+ jobs:
79
+ deploy:
80
+ needs: [build, integration-tests]
81
+ if: |
82
+ always() &&
83
+ needs.build.result == 'success' &&
84
+ (needs.integration-tests.result == 'success' ||
85
+ needs.integration-tests.result == 'skipped')
86
+ runs-on: ubuntu-latest
87
+ steps:
88
+ - run: ./deploy.sh
89
+ prevention:
90
+ - "Add `always() &&` to any job `if:` condition where upstream `needs` jobs may be skipped."
91
+ - "Treat a skipped upstream need as equivalent to a failed need unless `always()` is used."
92
+ - "Map your workflow DAG — anywhere a branch is optionally skipped, downstream jobs need `always()`."
93
+ - "Use `actionlint` to surface potential needs/if interaction issues statically."
94
+ docs:
95
+ - url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idif"
96
+ label: "jobs.<job_id>.if — Workflow syntax"
97
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-conditions-to-control-job-execution#using-status-check-functions"
98
+ label: "Using status check functions (always, failure, success, cancelled)"
99
+ - url: "https://github.com/actions/runner/issues/491"
100
+ label: "actions/runner#491 — Job if condition not evaluated correctly if needs job is skipped"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@htekdev/actions-debugger",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "65+ real GitHub Actions errors, queryable by agents. MCP server + Copilot skills + error database.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",