@htekdev/actions-debugger 1.0.69 → 1.0.70

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,124 @@
1
+ id: permissions-auth-046
2
+ title: 'actions/create-github-app-token output token expires after 1 hour — long-running jobs hit 401 Unauthorized'
3
+ category: permissions-auth
4
+ severity: error
5
+ tags:
6
+ - github-app
7
+ - installation-token
8
+ - token-expiry
9
+ - create-github-app-token
10
+ - long-running-jobs
11
+ patterns:
12
+ - regex: '401 Unauthorized'
13
+ flags: 'i'
14
+ - regex: 'Bad credentials'
15
+ flags: 'i'
16
+ - regex: 'installation.*token.*expired'
17
+ flags: 'i'
18
+ - regex: 'HttpError: Bad credentials'
19
+ flags: 'i'
20
+ error_messages:
21
+ - '401 Unauthorized'
22
+ - 'Bad credentials'
23
+ - 'HttpError: Bad credentials'
24
+ - 'Installation tokens expire one hour from the time you create them'
25
+ root_cause: |
26
+ GitHub App installation tokens — generated by actions/create-github-app-token — have a fixed
27
+ 1-hour expiry enforced by the GitHub platform. The token is generated once at the step where
28
+ the action runs, stored as a step output, and typically exported to an env var. If subsequent
29
+ steps take longer than 60 minutes to reach the point where the token is used, all GitHub API
30
+ calls and authenticated git operations will return 401 Unauthorized or "Bad credentials".
31
+
32
+ From GitHub's REST API docs:
33
+ "Installation tokens expire one hour from the time you create them. Using an expired token
34
+ produces a status code of 401 - Unauthorized, and requires creating a new installation token."
35
+
36
+ Common affected patterns:
37
+ - Long test suites (>60 min) where a GitHub App token is generated upfront for a final
38
+ PR comment or status check post at the end.
39
+ - Build pipelines that generate an app token for private package registry authentication
40
+ at the start, then run a multi-hour compilation and upload step at the end.
41
+ - Self-hosted runners on slow networks where upload + deploy sequences exceed 60 minutes.
42
+ - Jobs that poll or sleep mid-run (e.g., waiting for an external deployment to stabilize).
43
+
44
+ Important: The built-in GITHUB_TOKEN is NOT subject to this expiry — it remains valid for
45
+ the entire job duration (up to the job's timeout-minutes). Only tokens from
46
+ actions/create-github-app-token or direct GitHub App JWT exchanges expire after 1 hour.
47
+ fix: |
48
+ Generate a fresh GitHub App token immediately before the step that needs it rather than
49
+ once at the beginning of the job. For jobs that have multiple distinct phases requiring
50
+ the token, generate a separate token for each phase. Alternatively, split long-running
51
+ work into shorter jobs — each job generates its own fresh token on startup.
52
+ fix_code:
53
+ - language: yaml
54
+ label: 'Generate token immediately before the step that needs it (not at job start)'
55
+ code: |
56
+ jobs:
57
+ long-build:
58
+ runs-on: ubuntu-latest
59
+ steps:
60
+ - name: Checkout
61
+ uses: actions/checkout@v4
62
+
63
+ - name: Run long test suite
64
+ # This may run for over an hour
65
+ run: make test
66
+
67
+ # Generate a fresh token just before the API call — never stale
68
+ - name: Generate GitHub App token
69
+ id: app-token
70
+ uses: actions/create-github-app-token@v2
71
+ with:
72
+ app-id: ${{ vars.APP_ID }}
73
+ private-key: ${{ secrets.APP_PRIVATE_KEY }}
74
+
75
+ - name: Post PR comment
76
+ env:
77
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
78
+ run: |
79
+ gh pr comment ${{ github.event.pull_request.number }} \
80
+ --body "Tests completed successfully"
81
+
82
+ - language: yaml
83
+ label: 'Split into sub-jobs so each job generates its own short-lived token'
84
+ code: |
85
+ jobs:
86
+ long-tests:
87
+ runs-on: ubuntu-latest
88
+ steps:
89
+ - uses: actions/checkout@v4
90
+ - run: make test
91
+
92
+ notify:
93
+ runs-on: ubuntu-latest
94
+ needs: [long-tests]
95
+ steps:
96
+ # Each job generates a token on startup — always fresh
97
+ - name: Generate GitHub App token
98
+ id: app-token
99
+ uses: actions/create-github-app-token@v2
100
+ with:
101
+ app-id: ${{ vars.APP_ID }}
102
+ private-key: ${{ secrets.APP_PRIVATE_KEY }}
103
+
104
+ - name: Post PR comment
105
+ env:
106
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
107
+ run: |
108
+ gh pr comment ${{ github.event.pull_request.number }} \
109
+ --body "Tests completed successfully"
110
+ prevention:
111
+ - "Never store a GitHub App installation token at job start assuming it stays valid — generate it immediately before use"
112
+ - "For jobs expected to run longer than 45 minutes, split into sub-jobs so each generates its own fresh token"
113
+ - "Prefer the built-in GITHUB_TOKEN for operations within the same repository — it is valid for the entire job duration"
114
+ - "Set job-level timeout-minutes to catch unexpectedly long jobs and keep total runtime well under the 1-hour token expiry"
115
+ - "The actions/create-github-app-token README prominently warns: 'An installation access token expires after 1 hour'"
116
+ docs:
117
+ - url: 'https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app'
118
+ label: 'Generating an installation access token — expires after 1 hour (GitHub Docs)'
119
+ - url: 'https://github.com/actions/create-github-app-token'
120
+ label: 'actions/create-github-app-token — README warning about 1-hour expiry'
121
+ - url: 'https://github.com/actions/create-github-app-token/issues/128'
122
+ label: 'actions/create-github-app-token#128 — token only lasts 1 hour, long-running jobs fail'
123
+ - url: 'https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication'
124
+ label: 'GITHUB_TOKEN automatic token authentication — valid for entire job duration'
@@ -0,0 +1,90 @@
1
+ id: silent-failures-068
2
+ title: 'steps.<id>.conclusion is always success when continue-on-error: true — using conclusion to detect step failure never triggers'
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - continue-on-error
7
+ - steps-context
8
+ - outcome
9
+ - conclusion
10
+ - if-condition
11
+ - step-failure
12
+ patterns:
13
+ - regex: 'steps\.\w+\.conclusion\s*==\s*[''"]failure[''"]'
14
+ flags: 'i'
15
+ - regex: 'continue-on-error.*true'
16
+ flags: 'i'
17
+ error_messages:
18
+ - 'steps.<id>.conclusion == ''failure'' never evaluates to true'
19
+ - 'cleanup step silently skipped despite step failure'
20
+ root_cause: |
21
+ GitHub Actions provides two properties on the `steps` context for inspecting a completed step:
22
+
23
+ - `steps.<id>.outcome` — the raw result BEFORE `continue-on-error` is applied. Values:
24
+ `success`, `failure`, `cancelled`, `skipped`.
25
+ - `steps.<id>.conclusion` — the final result AFTER `continue-on-error` is applied. When
26
+ `continue-on-error: true` and the step fails, `conclusion` is `success` (not `failure`).
27
+
28
+ When a step has `continue-on-error: true` set, a step failure does NOT propagate to
29
+ `conclusion`. Developers who write:
30
+
31
+ if: steps.my-step.conclusion == 'failure'
32
+
33
+ expecting to detect when `my-step` failed will find that this condition is NEVER true — even
34
+ when the step actually failed — because `continue-on-error` overwrites the conclusion to
35
+ `success`. The downstream step silently never runs.
36
+
37
+ This commonly affects:
38
+ - Cleanup steps that should run when a flaky test step fails (continue-on-error: true)
39
+ - Notification steps that should alert on specific step failures in a pipeline
40
+ - Conditional logic that differentiates between "step was skipped" and "step failed"
41
+ fix: |
42
+ Use `steps.<id>.outcome` (not `conclusion`) to detect the raw result of a step that has
43
+ `continue-on-error: true`. The `outcome` property reflects the actual step result before
44
+ the error suppression is applied and correctly returns `failure` even when the final
45
+ conclusion is `success`.
46
+ fix_code:
47
+ - language: yaml
48
+ label: 'WRONG: conclusion is always success when continue-on-error is true — cleanup never runs'
49
+ code: |
50
+ steps:
51
+ - name: Run flaky tests
52
+ id: tests
53
+ continue-on-error: true
54
+ run: pytest tests/
55
+
56
+ - name: Send failure notification
57
+ # NEVER RUNS — conclusion is 'success' even when tests fail
58
+ if: steps.tests.conclusion == 'failure'
59
+ run: echo "Tests failed — notifying team"
60
+
61
+ - language: yaml
62
+ label: 'CORRECT: use outcome to detect the raw step result before continue-on-error'
63
+ code: |
64
+ steps:
65
+ - name: Run flaky tests
66
+ id: tests
67
+ continue-on-error: true
68
+ run: pytest tests/
69
+
70
+ - name: Send failure notification
71
+ # CORRECT — outcome reflects the raw result before continue-on-error override
72
+ if: steps.tests.outcome == 'failure'
73
+ run: echo "Tests failed — notifying team"
74
+
75
+ - name: Always-run cleanup
76
+ # For cleanup that must run regardless, use always() + outcome check
77
+ if: always() && steps.tests.outcome != 'success'
78
+ run: echo "Running cleanup after non-success outcome"
79
+ prevention:
80
+ - "When a step has continue-on-error: true, always use steps.<id>.outcome (not conclusion) to detect failures in downstream if conditions"
81
+ - "steps.<id>.conclusion == 'success' is always true when continue-on-error: true — it is safe for 'did this step not skip?' checks only"
82
+ - "The failure() status function is also bypassed by continue-on-error — use steps.<id>.outcome != 'success' instead"
83
+ - "Review GitHub Docs context reference table to understand outcome vs conclusion semantics before writing conditional cleanup steps"
84
+ docs:
85
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#steps-context'
86
+ label: 'steps context — outcome vs conclusion properties with continue-on-error (GitHub Docs)'
87
+ - url: 'https://github.com/actions/runner/blob/main/docs/adrs/0274-step-outcome-and-conclusion.md'
88
+ label: 'actions/runner ADR: outcome vs conclusion design — why two separate properties exist'
89
+ - url: 'https://stackoverflow.com/questions/71433926/how-do-to-run-the-next-github-action-step-even-if-the-previous-step-failed-whil'
90
+ label: 'Stack Overflow: using steps.outcome vs steps.conclusion with continue-on-error'
@@ -0,0 +1,106 @@
1
+ id: triggers-049
2
+ title: 'on.release types: [published] fires for pre-release creation AND pre-release-to-full promotion — deploy workflows run twice'
3
+ category: triggers
4
+ severity: silent-failure
5
+ tags:
6
+ - release
7
+ - published
8
+ - prerelease
9
+ - on-release
10
+ - double-trigger
11
+ - deploy
12
+ patterns:
13
+ - regex: 'github\.event\.action.*prereleased'
14
+ flags: 'i'
15
+ - regex: 'github\.event\.release\.prerelease.*true'
16
+ flags: 'i'
17
+ error_messages:
18
+ - 'github.event.action: prereleased'
19
+ - 'github.event.action: released'
20
+ - 'github.event.release.prerelease: true'
21
+ root_cause: |
22
+ The GitHub `release` event supports multiple activity types: `created`, `published`,
23
+ `prereleased`, `released`, `edited`, `deleted`, and `unpublished`. The `published` type
24
+ is frequently misunderstood:
25
+
26
+ - `published` fires whenever ANY release is made public — this includes both pre-releases
27
+ (action=prereleased) and full releases (action=released). It is a superset, not a
28
+ specific activity type for "final" releases.
29
+
30
+ A typical release lifecycle that triggers `on.release.types: [published]` twice:
31
+ 1. Developer creates a pre-release (e.g., v1.2.0-rc.1) and clicks "Publish release"
32
+ → workflow fires (github.event.action=prereleased, github.event.release.prerelease=true)
33
+ 2. Developer edits the release and unchecks "Set as a pre-release" to promote it to a
34
+ full release → workflow fires again (github.event.action=released, github.event.release.prerelease=false)
35
+
36
+ Additionally, the GitHub docs note that `prereleased` does NOT fire for pre-releases
37
+ published from a draft release, but `published` DOES fire — creating another asymmetry.
38
+
39
+ Workflows using `on.release.types: [published]` for deployment, package publishing,
40
+ or Docker image tagging will run twice for the same version tag, potentially creating
41
+ duplicate deployments, duplicate npm/PyPI publishes, or conflicting artifact versions.
42
+ fix: |
43
+ For production deploy workflows that should only run on final (non-pre-release) releases,
44
+ add an `if:` condition to check `github.event.release.prerelease == false`. Alternatively,
45
+ use `types: [released]` — which fires only when a stable release is published directly
46
+ or when a pre-release is promoted to full release, but NOT on initial pre-release creation.
47
+
48
+ To target pre-releases explicitly, use `types: [prereleased]`, but note it does not fire
49
+ for pre-releases published from drafts (use `published` + `if: github.event.release.prerelease == true`
50
+ in that case).
51
+ fix_code:
52
+ - language: yaml
53
+ label: 'Guard with if condition to skip pre-release publishes'
54
+ code: |
55
+ on:
56
+ release:
57
+ types: [published]
58
+
59
+ jobs:
60
+ deploy:
61
+ runs-on: ubuntu-latest
62
+ # Only deploy for final (non-pre-release) releases
63
+ if: github.event.release.prerelease == false
64
+ steps:
65
+ - name: Deploy release
66
+ run: echo "Deploying ${{ github.event.release.tag_name }}"
67
+
68
+ - language: yaml
69
+ label: 'Use released type to target only final releases (not pre-release creation)'
70
+ code: |
71
+ on:
72
+ release:
73
+ # released: fires when a release is published as final, OR when a pre-release
74
+ # is promoted to a full release. Does NOT fire on initial pre-release creation.
75
+ types: [released]
76
+
77
+ jobs:
78
+ deploy:
79
+ runs-on: ubuntu-latest
80
+ steps:
81
+ - name: Deploy release
82
+ run: echo "Deploying ${{ github.event.release.tag_name }}"
83
+
84
+ - language: yaml
85
+ label: 'Log release event fields at the start of release workflows for debugging'
86
+ code: |
87
+ jobs:
88
+ release-info:
89
+ runs-on: ubuntu-latest
90
+ steps:
91
+ - name: Debug release event
92
+ run: |
93
+ echo "action: ${{ github.event.action }}"
94
+ echo "prerelease: ${{ github.event.release.prerelease }}"
95
+ echo "tag: ${{ github.event.release.tag_name }}"
96
+ echo "draft: ${{ github.event.release.draft }}"
97
+ prevention:
98
+ - "Prefer types: [released] over types: [published] for production deployment workflows"
99
+ - "Always guard release deploy steps with: if: github.event.release.prerelease == false"
100
+ - "Make deployment and publish workflows idempotent so accidental double-runs cannot cause irreversible side effects"
101
+ - "Log github.event.action and github.event.release.prerelease at workflow start when debugging unexpected runs"
102
+ docs:
103
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#release'
104
+ label: 'release event activity types — published fires for both prereleased and released (GitHub Docs)'
105
+ - url: 'https://stackoverflow.com/questions/78085080/how-to-limit-a-github-action-to-fire-only-once-on-a-release-trigger'
106
+ label: 'Stack Overflow: release action triggered 3 times for pre-release — limit using types and if conditions'
@@ -0,0 +1,97 @@
1
+ id: yaml-syntax-045
2
+ title: 'Using paths and paths-ignore together on the same event trigger causes a workflow validation error'
3
+ category: yaml-syntax
4
+ severity: error
5
+ tags:
6
+ - paths
7
+ - paths-ignore
8
+ - trigger-filter
9
+ - on-push
10
+ - validation-error
11
+ patterns:
12
+ - regex: "You can't use both 'paths' and 'paths-ignore'"
13
+ flags: 'i'
14
+ - regex: 'Invalid workflow file.*paths.*paths-ignore'
15
+ flags: 'i'
16
+ - regex: 'paths.*and.*paths-ignore.*cannot.*same event'
17
+ flags: 'i'
18
+ error_messages:
19
+ - "Invalid workflow file: You can't use both 'paths' and 'paths-ignore' for the same event trigger"
20
+ - "You can't use both 'paths' and 'paths-ignore' for the same event trigger"
21
+ - "Path and Path-ignore cannot be added in the same event"
22
+ root_cause: |
23
+ GitHub Actions enforces a mutual exclusivity rule: `paths` and `paths-ignore` cannot be
24
+ specified simultaneously for the same event trigger. The same rule applies to:
25
+ - `branches` and `branches-ignore`
26
+ - `tags` and `tags-ignore`
27
+
28
+ `paths` is an allowlist — the trigger only fires when at least one of the listed patterns
29
+ matches a changed file. `paths-ignore` is a denylist — the trigger is suppressed when ALL
30
+ changed files match patterns in the list. Because their semantics are complementary, GitHub
31
+ disallows combining them to avoid ambiguous filter behavior.
32
+
33
+ Attempting to use both results in a validation error at workflow parse time:
34
+ "Invalid workflow file: You can't use both 'paths' and 'paths-ignore' for the same event trigger"
35
+
36
+ Developers commonly hit this when they want to trigger on a specific directory but exclude
37
+ documentation or test-only changes within that directory, assuming both filters can be
38
+ stacked like CSS selectors.
39
+
40
+ Example of the problematic pattern:
41
+ on:
42
+ push:
43
+ paths:
44
+ - 'src/**'
45
+ paths-ignore: # INVALID: cannot combine with paths
46
+ - 'src/**/*.md'
47
+ fix: |
48
+ Use only one of `paths` or `paths-ignore`. To achieve both include and exclude behavior
49
+ within a single filter:
50
+ - Use the `paths` filter with `!` negation patterns to explicitly exclude paths from an
51
+ allowlist. List all positive patterns before negative patterns — order matters.
52
+ - Alternatively, use `paths-ignore` alone if you just need to exclude a small set of paths
53
+ and want the workflow to run for everything else.
54
+ fix_code:
55
+ - language: yaml
56
+ label: 'WRONG: paths and paths-ignore combined (validation error)'
57
+ code: |
58
+ # This is INVALID — causes "You can't use both" validation error
59
+ on:
60
+ push:
61
+ paths:
62
+ - 'src/**'
63
+ paths-ignore:
64
+ - 'src/**/*.md'
65
+
66
+ - language: yaml
67
+ label: 'CORRECT: use paths with negation pattern to exclude within an allowlist'
68
+ code: |
69
+ # Use ! negation in paths to exclude specific sub-paths from the allowlist
70
+ on:
71
+ push:
72
+ paths:
73
+ - 'src/**' # include all changes under src/
74
+ - '!src/**/*.md' # but exclude markdown files within src/
75
+
76
+ - language: yaml
77
+ label: 'CORRECT: use only paths-ignore to suppress trigger for specific paths'
78
+ code: |
79
+ # If the workflow should run for everything EXCEPT docs, use paths-ignore alone
80
+ on:
81
+ push:
82
+ paths-ignore:
83
+ - 'docs/**'
84
+ - '**/*.md'
85
+ - '**/*.txt'
86
+ prevention:
87
+ - "Never combine paths with paths-ignore (or branches/branches-ignore, or tags/tags-ignore) for the same event"
88
+ - "To include a directory but exclude sub-paths: use paths with ! negation entries"
89
+ - "Use actionlint to validate workflow YAML locally before pushing — it catches this misconfiguration"
90
+ - "The same mutual exclusivity rule applies to branches/branches-ignore and tags/tags-ignore"
91
+ docs:
92
+ - url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onpushpull_requestpull_request_targetpaths'
93
+ label: 'on.push.paths — cannot be used with paths-ignore for the same event (GitHub Docs)'
94
+ - url: 'https://stackoverflow.com/questions/64896276/github-action-trigger-pipeline-for-root-folder-except-one-sub-folder'
95
+ label: 'Stack Overflow: "Path and Path-ignore cannot be added in the same event" — use ! negation in paths instead'
96
+ - url: 'https://github.com/rhysd/actionlint'
97
+ label: 'actionlint — static checker for GitHub Actions workflow files'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@htekdev/actions-debugger",
3
- "version": "1.0.69",
3
+ "version": "1.0.70",
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",