@htekdev/actions-debugger 1.0.106 → 1.0.108

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,90 @@
1
+ id: caching-artifacts-063
2
+ title: "runner.os insufficient in cache key after ubuntu-22.04 to ubuntu-24.04 migration — glibc-incompatible binaries silently restored"
3
+ category: caching-artifacts
4
+ severity: silent-failure
5
+ tags:
6
+ - cache-key
7
+ - runner.os
8
+ - ubuntu-24.04
9
+ - binary-compatibility
10
+ - glibc
11
+ - migration
12
+ patterns:
13
+ - regex: 'symbol lookup error.*undefined symbol'
14
+ flags: 'i'
15
+ - regex: 'version.*GLIBC.*not found'
16
+ flags: 'i'
17
+ - regex: 'GLIBCXX.*not found'
18
+ flags: 'i'
19
+ error_messages:
20
+ - "symbol lookup error: ./bin/app: undefined symbol: __libc_single_threaded"
21
+ - "version `GLIBC_2.38' not found (required by ./bin/app)"
22
+ - "GLIBCXX_3.4.32 not found in /usr/lib/x86_64-linux-gnu/libstdc++.so.6"
23
+ root_cause: |
24
+ `runner.os` returns "Linux" for ALL Linux-based GitHub-hosted runners regardless of
25
+ Ubuntu distribution version: ubuntu-22.04 (glibc 2.35, GCC 11) and ubuntu-24.04
26
+ (glibc 2.39, GCC 13) both return "Linux".
27
+
28
+ When a workflow migrates from `runs-on: ubuntu-22.04` to `runs-on: ubuntu-latest`
29
+ (which became ubuntu-24.04 in March 2025), cache keys using only `${{ runner.os }}`
30
+ continue to match previously saved caches from ubuntu-22.04. Compiled native binaries
31
+ — Rust/Cargo target directories, CMake build outputs, Go CGO artifacts, Python
32
+ Cython-compiled extensions, pre-built Node.js native addons — are restored from
33
+ the ubuntu-22.04 cache onto an ubuntu-24.04 runner.
34
+
35
+ Because ubuntu-24.04 ships a newer glibc (2.39 vs 2.35) and updated libstdc++, binaries
36
+ compiled against the older ABI fail at runtime with dynamic linker errors:
37
+
38
+ symbol lookup error: ./bin/app: undefined symbol: __libc_single_threaded
39
+ version `GLIBC_2.38' not found (required by ./bin/app)
40
+
41
+ The cache restore step succeeds and shows a green checkmark. The failure surfaces only
42
+ when the restored binary is executed later in the pipeline, potentially hours into a
43
+ build. Because the compile step is skipped (cache hit), the error appears to come from
44
+ the execution step with no indication that a stale cross-version cache is the cause.
45
+ fix: |
46
+ Add the runner image version to the cache key so that binary artifacts are not shared
47
+ across different Ubuntu distribution versions. GitHub-hosted runners expose the image
48
+ version as the `ImageVersion` environment variable (e.g. "20250312.1"). Alternatively,
49
+ hash `/etc/os-release` as a portable OS version fingerprint.
50
+ fix_code:
51
+ - language: yaml
52
+ label: "Include ImageVersion in cache key to isolate per Ubuntu release"
53
+ code: |
54
+ - name: Cache Cargo build artifacts
55
+ uses: actions/cache@v4
56
+ with:
57
+ path: |
58
+ ~/.cargo/registry
59
+ ~/.cargo/git
60
+ target/
61
+ # ImageVersion (e.g. "20250312.1") differs between ubuntu-22.04 and ubuntu-24.04
62
+ # Prevents restoring ubuntu-22.04 glibc-2.35 binaries on ubuntu-24.04 glibc-2.39
63
+ key: ${{ runner.os }}-${{ env.ImageVersion }}-cargo-${{ hashFiles('**/Cargo.lock') }}
64
+ restore-keys: |
65
+ ${{ runner.os }}-${{ env.ImageVersion }}-cargo-
66
+ - language: yaml
67
+ label: "Alternative: hash /etc/os-release for portable OS version fingerprint"
68
+ code: |
69
+ - name: Cache compiled artifacts
70
+ uses: actions/cache@v4
71
+ with:
72
+ path: build/
73
+ key: >-
74
+ ${{ runner.os }}-
75
+ ${{ hashFiles('/etc/os-release') }}-
76
+ cmake-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt') }}
77
+ restore-keys: |
78
+ ${{ runner.os }}-${{ hashFiles('/etc/os-release') }}-cmake-
79
+ prevention:
80
+ - "After migrating `runs-on` from a specific ubuntu version to `ubuntu-latest`, bust existing caches by adding a new key prefix or bumping a cache version counter"
81
+ - "Include `${{ env.ImageVersion }}` in cache keys whenever the cached content contains compiled native binaries"
82
+ - "Use `runner.arch` for x64/ARM64 differences and `ImageVersion` for OS version differences together for complete binary cache isolation"
83
+ - "Prefer caching dependency manifests and source-level artifacts over compiled binaries when cross-version compatibility is uncertain"
84
+ docs:
85
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows"
86
+ label: "GitHub Actions — caching dependencies"
87
+ - url: "https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md"
88
+ label: "ubuntu-24.04 runner image specification (glibc and GCC versions)"
89
+ - url: "https://github.blog/changelog/2025-03-05-github-actions-ubuntu-latest-is-now-ubuntu-24-04/"
90
+ label: "GitHub Changelog — ubuntu-latest is now ubuntu-24.04 (March 2025)"
@@ -0,0 +1,70 @@
1
+ id: permissions-auth-061
2
+ title: "actions/create-github-app-token v2 — default token scope limited to current repository only"
3
+ category: permissions-auth
4
+ severity: error
5
+ tags:
6
+ - github-app
7
+ - create-github-app-token
8
+ - multi-repo
9
+ - token-scope
10
+ - v2-migration
11
+ patterns:
12
+ - regex: 'Resource not accessible by integration'
13
+ flags: 'i'
14
+ - regex: 'actions/create-github-app-token@v2'
15
+ flags: 'i'
16
+ error_messages:
17
+ - "Resource not accessible by integration"
18
+ - "RequestError [HttpError]: Resource not accessible by integration"
19
+ root_cause: |
20
+ actions/create-github-app-token underwent a breaking scope change in v2 (released 2024).
21
+
22
+ In v1: when no `repositories` input is specified, the generated token is scoped to ALL
23
+ repositories the GitHub App installation has access to.
24
+
25
+ In v2: when no `repositories` or `owner` input is specified, the token is scoped ONLY
26
+ to the current repository where the workflow is running. This matches the GitHub App
27
+ installation token API default behavior when requesting a per-repository token.
28
+
29
+ Workflows that used v1 and relied on the token to access other repositories (for
30
+ cross-repo clones, API calls, pushes, or artifact publishing) silently receive a
31
+ limited-scope token in v2. Operations on other repos return "Resource not accessible
32
+ by integration" or "HTTP 404 Not Found" with no clear indication that a v2 migration
33
+ caused the regression.
34
+ fix: |
35
+ Explicitly declare the repositories the token should cover using the `repositories`
36
+ input (comma-separated repository names, without the org prefix). To grant access to
37
+ all repositories the app has access to within the same organization, use the `owner`
38
+ input instead.
39
+ fix_code:
40
+ - language: yaml
41
+ label: "Explicit multi-repo token scope (v2)"
42
+ code: |
43
+ - name: Generate token
44
+ id: app-token
45
+ uses: actions/create-github-app-token@v2
46
+ with:
47
+ app-id: ${{ vars.APP_ID }}
48
+ private-key: ${{ secrets.PRIVATE_KEY }}
49
+ # List every repository the token needs access to
50
+ repositories: "repo-a,repo-b,infra-configs"
51
+ - language: yaml
52
+ label: "Org-wide token scope using owner input (v2)"
53
+ code: |
54
+ - name: Generate token
55
+ id: app-token
56
+ uses: actions/create-github-app-token@v2
57
+ with:
58
+ app-id: ${{ vars.APP_ID }}
59
+ private-key: ${{ secrets.PRIVATE_KEY }}
60
+ # Grants access to all repos the app has access to in the org
61
+ owner: ${{ github.repository_owner }}
62
+ prevention:
63
+ - "When upgrading from v1 to v2, audit all usages for cross-repo operations and add an explicit `repositories:` or `owner:` input"
64
+ - "Pin to a major version tag and review the CHANGELOG before upgrading any action that generates authentication tokens"
65
+ - "Test cross-repo operations in a staging workflow immediately after a major version upgrade"
66
+ docs:
67
+ - url: "https://github.com/actions/create-github-app-token/releases/tag/v2.0.0"
68
+ label: "actions/create-github-app-token v2.0.0 release notes (scope change)"
69
+ - url: "https://github.com/actions/create-github-app-token/blob/main/README.md#inputs"
70
+ label: "actions/create-github-app-token README — repositories and owner inputs"
@@ -0,0 +1,116 @@
1
+ id: runner-environment-176
2
+ title: "hashFiles() fails on macOS with 'Fail to hash files under directory'"
3
+ category: runner-environment
4
+ severity: error
5
+ tags:
6
+ - hashfiles
7
+ - macos
8
+ - runner-images
9
+ - cache
10
+ - expression
11
+ - image-regression
12
+ patterns:
13
+ - regex: 'hashFiles\(.+\) failed\. Fail to hash files under directory'
14
+ flags: 'i'
15
+ - regex: 'The template is not valid.*hashFiles.*Fail to hash files'
16
+ flags: 'is'
17
+ error_messages:
18
+ - "Error: The template is not valid. /Users/runner/work/.../action.yml (Line: 48, Col: 12): hashFiles('**/go.sum') failed. Fail to hash files under directory '/Users/runner/work/...'"
19
+ - "hashFiles('**/package-lock.json') failed. Fail to hash files under directory '/Users/runner/work/...'"
20
+ root_cause: |
21
+ Certain macOS runner image versions (including macos-15-arm64 image
22
+ 20251119.0020 through approximately 20251122) introduced a regression
23
+ where the hashFiles() expression function fails to enumerate files
24
+ under the workspace directory on macOS. The same workflow succeeds on
25
+ Linux and Windows runners.
26
+
27
+ The failure manifests whenever hashFiles() is used in any expression
28
+ context — strategy.matrix, cache key, conditional — on the affected
29
+ macOS runner image versions. The underlying cause is a filesystem
30
+ enumeration permission or path-resolution change introduced by the
31
+ runner image update (see actions/runner-images #13341). The issue was
32
+ not present in the previous macOS image version and reappeared without
33
+ workflow changes.
34
+
35
+ Root cause in runner-images: the runner image update changed system
36
+ configuration in a way that prevented the runner process from
37
+ recursively listing files under the workspace directory on macOS. The
38
+ fix was a runner image rollback / patch released around December 12,
39
+ 2025.
40
+
41
+ This failure is a runner-image regression, not a workflow bug. It can
42
+ recur if future macOS runner image updates introduce similar filesystem
43
+ configuration changes.
44
+ fix: |
45
+ If you encounter this error on macOS:
46
+
47
+ 1. Wait for GitHub to release a patched macOS runner image — runner
48
+ image regressions are typically resolved within days. Monitor
49
+ actions/runner-images for the fix.
50
+
51
+ 2. Pin your workflow to a specific macOS runner image version that
52
+ predates the regression while waiting for the fix.
53
+
54
+ 3. As a temporary workaround, replace hashFiles() with a manual
55
+ hash computation step using sha256sum or similar:
56
+
57
+ - name: Compute hash manually (macOS workaround)
58
+ id: hash
59
+ run: |
60
+ echo "hash=$(find . -name 'go.sum' -exec sha256sum {} \; | sha256sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT
61
+ - uses: actions/cache@v4
62
+ with:
63
+ path: ~/go/pkg/mod
64
+ key: ${{ runner.os }}-go-${{ steps.hash.outputs.hash }}
65
+
66
+ 4. After the runner image is updated to a fixed version, remove the
67
+ workaround and restore hashFiles().
68
+ fix_code:
69
+ - language: yaml
70
+ label: "Manual hash workaround for macOS hashFiles() regression"
71
+ code: |
72
+ jobs:
73
+ build:
74
+ runs-on: macos-latest
75
+
76
+ steps:
77
+ - uses: actions/checkout@v4
78
+
79
+ # Workaround: compute cache key hash manually when hashFiles() fails on macOS
80
+ - name: Compute go.sum hash
81
+ id: hash
82
+ run: |
83
+ echo "hash=$(find . -name 'go.sum' | sort | xargs sha256sum | sha256sum | cut -d' ' -f1)" \
84
+ >> "$GITHUB_OUTPUT"
85
+
86
+ - uses: actions/cache@v4
87
+ with:
88
+ path: ~/go/pkg/mod
89
+ key: ${{ runner.os }}-go-${{ steps.hash.outputs.hash }}
90
+
91
+ - run: go build ./...
92
+ - language: yaml
93
+ label: "Pin to a known-good macOS runner image while waiting for fix"
94
+ code: |
95
+ jobs:
96
+ build:
97
+ # Pin to a specific macOS runner image version that predates the regression.
98
+ # Check https://github.com/actions/runner-images/releases for available versions.
99
+ runs-on: macos-15-arm64
100
+ # Alternatively, use an older pinned version: macos-14 or macos-15-xlarge
101
+ # when the current macos-latest image has the regression.
102
+ prevention:
103
+ - "Subscribe to actions/runner-images release notifications to detect image regressions early."
104
+ - "When hashFiles() fails only on macOS (Linux/Windows succeed), suspect a runner image regression
105
+ rather than a workflow bug — check the actions/runner-images issue tracker for reports."
106
+ - "If CI budget allows, run a short nightly workflow that tests hashFiles() on all target OS
107
+ combinations to detect image regressions before they block your main CI."
108
+ - "When pinning a runner image version as a workaround, add a TODO comment and a link to the
109
+ upstream issue so the pin is removed once the fix is released."
110
+ docs:
111
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#hashfiles"
112
+ label: "GitHub Actions: hashFiles() expression function"
113
+ - url: "https://github.com/actions/runner/issues/4134"
114
+ label: "actions/runner #4134: hashFiles on macOS fails with 'Fail to hash files under directory' (14 reactions, closed Dec 2025)"
115
+ - url: "https://github.com/actions/runner-images/issues/13341"
116
+ label: "actions/runner-images #13341: macOS hashFiles regression report (Nov 2025)"
@@ -0,0 +1,99 @@
1
+ id: silent-failures-095
2
+ title: "github.event.head_commit is null on non-push events — commit message checks silently return false"
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - github-context
7
+ - head-commit
8
+ - workflow-dispatch
9
+ - event-context
10
+ - commit-message
11
+ - null-context
12
+ patterns:
13
+ - regex: 'github\.event\.head_commit\.message'
14
+ flags: 'i'
15
+ - regex: 'github\.event\.head_commit\b'
16
+ flags: 'i'
17
+ error_messages:
18
+ - "contains(github.event.head_commit.message, ...) returns false on workflow_dispatch"
19
+ - "github.event.head_commit is null"
20
+ root_cause: |
21
+ The `github.event.head_commit` context object is only populated on `push` events.
22
+ On all other trigger types — `workflow_dispatch`, `pull_request`, `pull_request_target`,
23
+ `schedule`, `workflow_call`, `workflow_run`, `release`, and others — the property is
24
+ null, and all child fields evaluate to empty string `""` in expressions.
25
+
26
+ A very common workflow pattern is to gate deployment or release steps based on the
27
+ commit message:
28
+
29
+ if: contains(github.event.head_commit.message, '[deploy]')
30
+
31
+ When this condition is evaluated on a `workflow_dispatch` run, a scheduled run, or
32
+ any non-push trigger, `github.event.head_commit` is null and `contains()` receives
33
+ an empty string. The expression silently returns false and the step is skipped.
34
+
35
+ No error, no warning, and no indication in the workflow log that the condition was
36
+ never evaluated against real commit message data. Developers manually triggering the
37
+ workflow see their step silently skipped with no explanation.
38
+
39
+ This is particularly confusing because:
40
+ - The workflow completes successfully with a green checkmark
41
+ - The step appears in the log as skipped with no reason given
42
+ - Manual `workflow_dispatch` runs show no null-context warning
43
+ fix: |
44
+ Guard the commit message check with an explicit event type condition, or provide a
45
+ workflow_dispatch input as an equivalent gate for manual runs.
46
+ fix_code:
47
+ - language: yaml
48
+ label: "Guard commit message check to push events only"
49
+ code: |
50
+ jobs:
51
+ deploy:
52
+ runs-on: ubuntu-latest
53
+ steps:
54
+ - name: Deploy on tagged commit message
55
+ # Only evaluate head_commit.message on push events where it is defined
56
+ if: >-
57
+ github.event_name == 'push' &&
58
+ contains(github.event.head_commit.message, '[deploy]')
59
+ run: ./scripts/deploy.sh
60
+ - language: yaml
61
+ label: "Support push and manual dispatch with input fallback"
62
+ code: |
63
+ on:
64
+ push:
65
+ branches: [main]
66
+ workflow_dispatch:
67
+ inputs:
68
+ deploy:
69
+ description: 'Trigger deployment manually'
70
+ type: boolean
71
+ default: false
72
+
73
+ jobs:
74
+ deploy:
75
+ runs-on: ubuntu-latest
76
+ steps:
77
+ - name: Resolve deploy flag
78
+ id: flag
79
+ run: |
80
+ if [[ "${{ github.event_name }}" == "push" ]]; then
81
+ MSG="${{ github.event.head_commit.message }}"
82
+ echo "enabled=$([[ "$MSG" == *'[deploy]'* ]] && echo true || echo false)" >> $GITHUB_OUTPUT
83
+ else
84
+ echo "enabled=${{ inputs.deploy }}" >> $GITHUB_OUTPUT
85
+ fi
86
+
87
+ - name: Deploy
88
+ if: steps.flag.outputs.enabled == 'true'
89
+ run: ./scripts/deploy.sh
90
+ prevention:
91
+ - "Never use `github.event.head_commit.*` without first checking `github.event_name == 'push'`"
92
+ - "Use `workflow_dispatch` inputs with boolean type as the manual-trigger equivalent of commit-message gates"
93
+ - "Document which events activate each step — add inline comments explaining gating conditions"
94
+ - "Test multi-trigger workflows by manually running them and verifying steps behave as expected"
95
+ docs:
96
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#push"
97
+ label: "GitHub Actions push event — head_commit availability"
98
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/contexts#github-context"
99
+ label: "GitHub context reference — github.event object"
@@ -0,0 +1,144 @@
1
+ id: silent-failures-094
2
+ title: "Reusable workflow input default: value silently ignored when caller passes empty string"
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - reusable-workflow
7
+ - workflow-call
8
+ - inputs
9
+ - default
10
+ - empty-string
11
+ - silent-failure
12
+ patterns:
13
+ - regex: 'on:\s*\n\s*workflow_call:'
14
+ flags: 'im'
15
+ - regex: 'inputs\.\w+\.default:'
16
+ flags: 'i'
17
+ - regex: 'with:\s*\n(?:\s+\w+:\s*\$\{\{[^}]+\}\}\s*\n)+'
18
+ flags: 'im'
19
+ error_messages:
20
+ - "Reusable workflow input default value not used when caller passes empty string"
21
+ - "Expected default message, got empty string"
22
+ - "inputs.message is empty even though default is set"
23
+ root_cause: |
24
+ The default: field in on.workflow_call.inputs is only applied when the input is
25
+ completely absent (not provided by the caller at all). It is NOT applied when the
26
+ caller explicitly passes an empty string "".
27
+
28
+ A very common pattern is a caller workflow that passes a workflow_dispatch input
29
+ directly to a reusable workflow:
30
+
31
+ # caller.yml
32
+ on:
33
+ workflow_dispatch:
34
+ inputs:
35
+ message:
36
+ type: string
37
+ required: false # defaults to "" when not provided
38
+
39
+ jobs:
40
+ call:
41
+ uses: ./.github/workflows/reusable.yml
42
+ with:
43
+ message: ${{ inputs.message }} # passes "" when dispatch runs without input
44
+
45
+ # reusable.yml
46
+ on:
47
+ workflow_call:
48
+ inputs:
49
+ message:
50
+ type: string
51
+ default: 'default message'
52
+
53
+ When a developer triggers the caller workflow_dispatch without providing the
54
+ message input, inputs.message resolves to "" (the workflow_dispatch default for
55
+ unset string inputs). The caller then passes "" to the reusable workflow. The
56
+ reusable workflow receives "" as an explicitly-provided value, so its own
57
+ default: 'default message' is never used. The job runs with message = "".
58
+
59
+ This is by design: GitHub Actions treats "" as a valid non-absent value. The
60
+ default: key only activates for truly absent inputs (when with: does not include
61
+ the key at all). Since ${{ inputs.message }} always resolves to some string (even
62
+ "", it is always present in the with: block, so the called workflow's default
63
+ is always bypassed.
64
+ fix: |
65
+ Move the default value handling to the calling workflow rather than the reusable
66
+ workflow, using a conditional expression to fall back to the default:
67
+
68
+ # caller.yml — handle empty-string fallback here
69
+ jobs:
70
+ call:
71
+ uses: ./.github/workflows/reusable.yml
72
+ with:
73
+ message: ${{ inputs.message != '' && inputs.message || 'default message' }}
74
+
75
+ Alternatively, handle the fallback inside the reusable workflow steps using an
76
+ env variable substitution:
77
+
78
+ # reusable.yml
79
+ jobs:
80
+ run:
81
+ runs-on: ubuntu-latest
82
+ env:
83
+ EFFECTIVE_MESSAGE: ${{ inputs.message != '' && inputs.message || 'default message' }}
84
+ steps:
85
+ - run: echo "Message is $EFFECTIVE_MESSAGE"
86
+
87
+ Do NOT rely on default: in on.workflow_call.inputs when the caller may pass ""
88
+ via ${{ inputs.x }} from a workflow_dispatch input.
89
+ fix_code:
90
+ - language: yaml
91
+ label: "Apply the default in the caller with a conditional expression (recommended)"
92
+ code: |
93
+ # caller.yml
94
+ on:
95
+ workflow_dispatch:
96
+ inputs:
97
+ message:
98
+ type: string
99
+ required: false
100
+
101
+ jobs:
102
+ call:
103
+ uses: ./.github/workflows/reusable.yml
104
+ with:
105
+ # Use || to fall back to default when inputs.message is empty
106
+ message: ${{ inputs.message != '' && inputs.message || 'default message' }}
107
+ - language: yaml
108
+ label: "Apply the fallback inside the reusable workflow using env:"
109
+ code: |
110
+ # reusable.yml
111
+ on:
112
+ workflow_call:
113
+ inputs:
114
+ message:
115
+ type: string
116
+ required: false
117
+ # Note: default: here is bypassed when caller passes ""
118
+
119
+ jobs:
120
+ run:
121
+ runs-on: ubuntu-latest
122
+ env:
123
+ # Handle empty string at job level — inputs.message may be "" not null
124
+ EFFECTIVE_MESSAGE: ${{ inputs.message != '' && inputs.message || 'default message' }}
125
+ steps:
126
+ - name: Use effective message
127
+ run: echo "Message is $EFFECTIVE_MESSAGE"
128
+ prevention:
129
+ - "Never place default values only in on.workflow_call.inputs.default when the caller
130
+ passes the input via ${{ inputs.x }} from a workflow_dispatch — the empty string
131
+ will always bypass the default."
132
+ - "Place fallback logic in the caller using '${{ inputs.x != '''''' && inputs.x || ''default'' }}'
133
+ to handle both absent and empty-string cases."
134
+ - "If you need the reusable workflow to have its own default, handle it via env: fallback
135
+ inside the job steps, not via on.workflow_call.inputs.default."
136
+ - "This behavior stems from GitHub Actions treating \"\" as an explicitly provided string
137
+ value rather than an absent/null input. Document this distinction for your team."
138
+ docs:
139
+ - url: "https://docs.github.com/en/actions/sharing-automations/reusing-workflows#using-inputs-and-secrets-in-a-reusable-workflow"
140
+ label: "GitHub Actions: Using inputs and secrets in a reusable workflow"
141
+ - url: "https://github.com/actions/runner/issues/2907"
142
+ label: "actions/runner #2907: Reusable workflow input default not used when caller passes empty string (16 reactions)"
143
+ - url: "https://github.com/actions/runner/issues/924"
144
+ label: "actions/runner #924: Differentiate between input parameter empty or not present (76 reactions, still open)"
@@ -0,0 +1,95 @@
1
+ id: silent-failures-096
2
+ title: "Job timeout-minutes sets result to 'cancelled' not 'failure' — if: failure() notification jobs silently skip timed-out jobs"
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - timeout-minutes
7
+ - job-result
8
+ - if-failure
9
+ - notification
10
+ - cancelled
11
+ - cleanup-job
12
+ patterns:
13
+ - regex: 'if:.*failure\(\)'
14
+ flags: 'i'
15
+ - regex: 'timeout-minutes:\s*\d+'
16
+ flags: 'i'
17
+ error_messages:
18
+ - "This step was skipped because a previous step failed"
19
+ - "notification job skipped — upstream job timed out but result is 'cancelled' not 'failure'"
20
+ root_cause: |
21
+ When a job exceeds its `timeout-minutes:` limit, GitHub Actions cancels the job and
22
+ sets its `result` to `"cancelled"` — NOT `"failure"`. This is a consistent but
23
+ widely misunderstood behavior.
24
+
25
+ A standard pattern is to add a notification or cleanup job that runs when builds fail:
26
+
27
+ notify:
28
+ needs: build
29
+ if: failure()
30
+ runs-on: ubuntu-latest
31
+
32
+ The `failure()` status check function returns true only when at least one upstream job
33
+ has result `"failure"`. It does NOT match `"cancelled"`. When `build` times out,
34
+ `needs.build.result` is `"cancelled"`, so `if: failure()` evaluates to false and
35
+ the notification job is silently skipped.
36
+
37
+ The four terminal job result values are: "success", "failure", "cancelled", "skipped".
38
+ The built-in functions map to:
39
+ - `failure()` → true when any upstream job result is "failure"
40
+ - `cancelled()` → true when any upstream job result is "cancelled"
41
+ - `success()` → true when all upstream jobs result in "success" (default)
42
+ - `always()` → always true regardless of upstream results
43
+
44
+ Developers are often surprised that a timed-out build does not trigger their alerting
45
+ job. The UI shows the timed-out job in orange ("Cancelled"), while the notification
46
+ job appears grey ("Skipped") — there is no direct indication that the skip is due to
47
+ the timeout rather than an unrelated condition.
48
+ fix: |
49
+ Expand the notification job condition to explicitly cover both `failure` and `cancelled`
50
+ results, using `always()` to ensure the job runs regardless of upstream outcomes.
51
+ fix_code:
52
+ - language: yaml
53
+ label: "Catch both failure and timeout (cancelled) in notification job"
54
+ code: |
55
+ jobs:
56
+ build:
57
+ runs-on: ubuntu-latest
58
+ timeout-minutes: 30
59
+ steps:
60
+ - uses: actions/checkout@v4
61
+ - run: make build
62
+
63
+ notify:
64
+ needs: build
65
+ # Use always() so the job runs; then check for failure OR cancelled (timeout)
66
+ if: always() && (needs.build.result == 'failure' || needs.build.result == 'cancelled')
67
+ runs-on: ubuntu-latest
68
+ steps:
69
+ - name: Send alert
70
+ run: |
71
+ echo "Build result: ${{ needs.build.result }}"
72
+ # call your notification webhook here
73
+ - language: yaml
74
+ label: "Multi-job pipeline — catch any non-success terminal state"
75
+ code: |
76
+ jobs:
77
+ report:
78
+ needs: [build, test, deploy]
79
+ if: >-
80
+ always() &&
81
+ (contains(needs.*.result, 'failure') ||
82
+ contains(needs.*.result, 'cancelled'))
83
+ runs-on: ubuntu-latest
84
+ steps:
85
+ - run: echo "Pipeline did not complete successfully"
86
+ prevention:
87
+ - "Replace bare `if: failure()` with `if: always() && (needs.X.result == 'failure' || needs.X.result == 'cancelled')` whenever upstream jobs have `timeout-minutes:` set"
88
+ - "Treat `timeout-minutes` as a cancellation mechanism — timed-out jobs report `cancelled` not `failure`"
89
+ - "Test your notification path by temporarily reducing `timeout-minutes` to confirm the alerting logic fires on timeout"
90
+ - "Document in team runbooks that CI timeouts appear orange ('Cancelled') not red ('Failed') in the Actions UI"
91
+ docs:
92
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/using-conditions-to-control-job-execution"
93
+ label: "GitHub Actions — status check functions (failure, cancelled, always)"
94
+ - url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idtimeout-minutes"
95
+ label: "GitHub Actions workflow syntax — timeout-minutes"
@@ -0,0 +1,137 @@
1
+ id: triggers-068
2
+ title: "pull_request paths/paths-ignore filter evaluates entire PR diff, not latest commit"
3
+ category: triggers
4
+ severity: silent-failure
5
+ tags:
6
+ - paths-filter
7
+ - paths-ignore
8
+ - pull_request
9
+ - three-dot-diff
10
+ - silent-trigger
11
+ - monorepo
12
+ patterns:
13
+ - regex: 'paths-ignore:'
14
+ flags: 'i'
15
+ - regex: 'on:\s*\n\s*pull_request:'
16
+ flags: 'im'
17
+ error_messages:
18
+ - "Workflow triggers on push to README.md even though README.md is listed in paths-ignore"
19
+ - "paths-ignore not respected after one commit touches files outside the ignore list"
20
+ - "Workflow keeps running for all subsequent commits in PR even though only ignored files changed"
21
+ root_cause: |
22
+ GitHub's paths and paths-ignore filters for pull_request (and pull_request_target)
23
+ events use a three-dot diff — a comparison between the most recent version of the
24
+ topic branch HEAD and the commit where the topic branch last diverged from the base
25
+ branch (the merge base). This diff encompasses ALL files changed across the entire
26
+ PR, not just the files changed in the most recent commit/push.
27
+
28
+ The practical consequence is: once any commit in the PR has touched a file that is
29
+ NOT in paths-ignore, the three-dot diff permanently includes that file for the
30
+ lifetime of the PR. All subsequent pushes to the same PR will also trigger the
31
+ workflow, even if those pushes only modify files listed in paths-ignore.
32
+
33
+ Example: a PR starts by pushing a Go source file (triggers the workflow). The
34
+ developer then pushes a README.md-only commit. Despite README.md being in
35
+ paths-ignore, the entire PR diff still includes the earlier Go change, so the
36
+ workflow fires again.
37
+
38
+ This behavior is documented under "Git diff comparisons" in the GitHub Actions
39
+ workflow syntax reference but is widely misunderstood.
40
+
41
+ Contrast with push events: push filters only examine files changed in the
42
+ individual push commit(s), so paths-ignore works as most developers expect.
43
+ fix: |
44
+ Option 1 — Use dorny/paths-filter to gate job execution at the step/job level
45
+ based on files changed only in the current push (most reliable workaround):
46
+
47
+ jobs:
48
+ guard:
49
+ runs-on: ubuntu-latest
50
+ outputs:
51
+ changed: ${{ steps.filter.outputs.src }}
52
+ steps:
53
+ - uses: actions/checkout@v4
54
+ - uses: dorny/paths-filter@v3
55
+ id: filter
56
+ with:
57
+ filters: |
58
+ src:
59
+ - 'src/**'
60
+ - '!README.md'
61
+
62
+ build:
63
+ needs: guard
64
+ if: needs.guard.outputs.changed == 'true'
65
+ runs-on: ubuntu-latest
66
+ steps:
67
+ - run: echo "only runs if src changed in latest push"
68
+
69
+ Option 2 — Accept that pull_request paths filtering is PR-wide and use it only
70
+ for coarse-grained "should this workflow category run at all" decisions, not for
71
+ per-commit granularity.
72
+
73
+ Option 3 — Remove paths/paths-ignore from the pull_request trigger entirely and
74
+ use conditional job-level steps with dorny/paths-filter instead.
75
+ fix_code:
76
+ - language: yaml
77
+ label: "Use dorny/paths-filter for per-commit path gating (recommended)"
78
+ code: |
79
+ on:
80
+ pull_request:
81
+ branches: [main]
82
+ # No paths/paths-ignore here — filter is done per-commit in the job
83
+
84
+ jobs:
85
+ check-changes:
86
+ runs-on: ubuntu-latest
87
+ outputs:
88
+ src_changed: ${{ steps.filter.outputs.src }}
89
+ steps:
90
+ - uses: actions/checkout@v4
91
+ - uses: dorny/paths-filter@v3
92
+ id: filter
93
+ with:
94
+ filters: |
95
+ src:
96
+ - 'src/**'
97
+ - '*.go'
98
+ - '!**/*.md'
99
+
100
+ build:
101
+ needs: check-changes
102
+ if: needs.check-changes.outputs.src_changed == 'true'
103
+ runs-on: ubuntu-latest
104
+ steps:
105
+ - uses: actions/checkout@v4
106
+ - run: make build
107
+ - language: yaml
108
+ label: "What NOT to do — paths-ignore on pull_request silently fails after first non-ignored commit"
109
+ code: |
110
+ # This looks correct but DOES NOT work as expected for pull_request events:
111
+ on:
112
+ pull_request:
113
+ paths-ignore:
114
+ - '**.md' # <-- evaluated against entire PR diff, not latest push
115
+ - 'docs/**'
116
+
117
+ # After any commit in the PR touches a non-md file, ALL subsequent commits
118
+ # trigger this workflow, even if those commits only change .md files.
119
+ prevention:
120
+ - "Do not rely on paths-ignore to prevent a workflow from running on documentation-only
121
+ follow-up commits in a PR — it only works until any non-ignored file is committed to
122
+ the PR branch."
123
+ - "Use dorny/paths-filter or similar per-commit path detection actions instead of workflow-level
124
+ paths-ignore for pull_request events when per-commit granularity is needed."
125
+ - "For monorepos, consider separate workflows per component triggered by dorny/paths-filter
126
+ outputs rather than native paths filters."
127
+ - "paths-ignore works reliably for push events (single commit diff) — the behavior difference
128
+ between push and pull_request is a common source of confusion."
129
+ docs:
130
+ - url: "https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#git-diff-comparisons"
131
+ label: "GitHub Actions workflow syntax: Git diff comparisons"
132
+ - url: "https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#onpushpull_requestpull_request_targetpathspaths-ignore"
133
+ label: "GitHub Actions: paths and paths-ignore filter syntax"
134
+ - url: "https://github.com/actions/runner/issues/2324"
135
+ label: "actions/runner #2324: on.pull_request.paths-ignore are not respected correctly (64 reactions, still open)"
136
+ - url: "https://github.com/dorny/paths-filter"
137
+ label: "dorny/paths-filter — per-commit path change detection action"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@htekdev/actions-debugger",
3
- "version": "1.0.106",
3
+ "version": "1.0.108",
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",