@htekdev/actions-debugger 1.0.55 → 1.0.57

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 (25) hide show
  1. package/errors/caching-artifacts/caching-artifacts-038.yml +95 -0
  2. package/errors/caching-artifacts/caching-artifacts-039.yml +110 -0
  3. package/errors/concurrency-timing/concurrency-timing-033.yml +104 -0
  4. package/errors/concurrency-timing/concurrency-timing-034.yml +123 -0
  5. package/errors/known-unsolved/known-unsolved-037.yml +124 -0
  6. package/errors/known-unsolved/known-unsolved-038.yml +124 -0
  7. package/errors/known-unsolved/known-unsolved-039.yml +102 -0
  8. package/errors/permissions-auth/permissions-auth-040.yml +142 -0
  9. package/errors/permissions-auth/permissions-auth-041.yml +110 -0
  10. package/errors/runner-environment/runner-environment-112.yml +98 -0
  11. package/errors/runner-environment/runner-environment-113.yml +118 -0
  12. package/errors/runner-environment/runner-environment-114.yml +130 -0
  13. package/errors/runner-environment/runner-environment-115.yml +120 -0
  14. package/errors/runner-environment/runner-environment-116.yml +106 -0
  15. package/errors/runner-environment/runner-environment-117.yml +109 -0
  16. package/errors/silent-failures/silent-failures-056.yml +105 -0
  17. package/errors/silent-failures/silent-failures-057.yml +120 -0
  18. package/errors/silent-failures/silent-failures-058.yml +126 -0
  19. package/errors/triggers/triggers-040.yml +104 -0
  20. package/errors/triggers/triggers-041.yml +105 -0
  21. package/errors/triggers/triggers-042.yml +110 -0
  22. package/errors/triggers/triggers-043.yml +125 -0
  23. package/errors/yaml-syntax/yaml-syntax-040.yml +135 -0
  24. package/errors/yaml-syntax/yaml-syntax-041.yml +147 -0
  25. package/package.json +1 -1
@@ -0,0 +1,95 @@
1
+ id: caching-artifacts-038
2
+ title: "hashFiles() returns empty string when no matching files found — constant cache key causes cross-branch cache pollution"
3
+ category: caching-artifacts
4
+ severity: silent-failure
5
+ tags:
6
+ - cache
7
+ - hashFiles
8
+ - cache-key
9
+ - silent-failure
10
+ - cross-branch
11
+ - lock-file
12
+ patterns:
13
+ - regex: 'hashfiles\s*\(.*\)\s*==\s*''"""'
14
+ flags: 'i'
15
+ - regex: 'cache-hit.*true.*hashfiles.*empty'
16
+ flags: 'i'
17
+ error_messages:
18
+ - "Cache restored successfully"
19
+ - "Cache hit: true"
20
+ root_cause: |
21
+ The `hashFiles()` expression function returns an empty string when no files match the
22
+ provided glob pattern. It does NOT throw an error or fail the step.
23
+
24
+ A common pattern is:
25
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
26
+
27
+ If `package-lock.json` does not exist (e.g., using yarn, pnpm, or no lock file present),
28
+ `hashFiles('**/package-lock.json')` returns `""` and the key becomes:
29
+ "Linux-node-"
30
+
31
+ This constant key means every workflow run hits the same cache entry regardless of
32
+ dependency changes or branch. The first job saves its node_modules under this key, and
33
+ every subsequent run on every branch restores those same cached modules. This silently
34
+ masks dependency update failures or serves stale packages from a different branch's state.
35
+
36
+ The failure is invisible: `cache-hit: true` appears in logs and the job succeeds, hiding
37
+ the fact that no lock file was actually hashed. Developers notice only when flaky test
38
+ failures or "wrong version installed" errors occur.
39
+ fix: |
40
+ Use the correct lock file for your package manager, or add a fallback to prevent a
41
+ constant key when hashFiles returns empty.
42
+
43
+ Option 1: Use the correct lock file:
44
+ - npm: **/package-lock.json
45
+ - yarn: **/yarn.lock
46
+ - pnpm: **/pnpm-lock.yaml
47
+ - pip: **/requirements.txt or **/poetry.lock
48
+
49
+ Option 2: Add a github.run_id fallback to ensure key uniqueness when files are missing:
50
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') || github.run_id }}
51
+
52
+ Option 3: Use hashFiles with multiple patterns so at least one matches:
53
+ key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json', '**/yarn.lock', '**/pnpm-lock.yaml') }}
54
+
55
+ Option 4: Add a validation step to fail fast if no lock file is found rather than
56
+ silently using a constant cache key.
57
+ fix_code:
58
+ - language: yaml
59
+ label: "run_id fallback prevents constant cache key when lock file is absent"
60
+ code: |
61
+ - name: Cache node modules
62
+ uses: actions/cache@v4
63
+ with:
64
+ path: ~/.npm
65
+ # hashFiles returns "" if no lock file found; run_id fallback prevents constant key
66
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') || github.run_id }}
67
+ restore-keys: |
68
+ ${{ runner.os }}-node-
69
+ - language: yaml
70
+ label: "Multi-ecosystem lock file glob — matches whichever lock file is present"
71
+ code: |
72
+ - name: Cache dependencies
73
+ uses: actions/cache@v4
74
+ with:
75
+ path: |
76
+ ~/.npm
77
+ ~/.yarn/cache
78
+ ~/.pnpm-store
79
+ key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json', '**/yarn.lock', '**/pnpm-lock.yaml') }}
80
+ restore-keys: |
81
+ ${{ runner.os }}-deps-
82
+ prevention:
83
+ - "Verify your lock file glob matches an actual file before relying on it for cache key uniqueness"
84
+ - "Add || github.run_id fallback: hashFiles('...') || github.run_id prevents silent constant-key caching"
85
+ - "Use restore-keys as a fallback strategy rather than depending on an exact key hit"
86
+ - "In setup steps, validate the expected lock file exists before caching (e.g., Test-Path package-lock.json)"
87
+ docs:
88
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#hashfiles"
89
+ label: "GitHub Docs — hashFiles() expression function"
90
+ - url: "https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows"
91
+ label: "GitHub Docs — Caching dependencies to speed up workflows"
92
+ - url: "https://github.com/actions/cache/blob/main/tips-and-workarounds.md"
93
+ label: "actions/cache — Tips and workarounds"
94
+ - url: "https://github.com/orgs/community/discussions/25064"
95
+ label: "GitHub Community — hashFiles returns empty when no files match"
@@ -0,0 +1,110 @@
1
+ id: caching-artifacts-039
2
+ title: "actions/cache split save/restore: path must match exactly or restore silently reports cache miss"
3
+ category: caching-artifacts
4
+ severity: silent-failure
5
+ tags:
6
+ - cache
7
+ - actions/cache
8
+ - save-restore
9
+ - path-mismatch
10
+ - cache-miss
11
+ - split-cache
12
+ patterns:
13
+ - regex: 'Cache not found for input keys'
14
+ flags: 'i'
15
+ - regex: 'cache/restore.*path.*not.*found'
16
+ flags: 'i'
17
+ error_messages:
18
+ - "Cache not found for input keys: <key>"
19
+ - "No cache found for key: <key>"
20
+ root_cause: |
21
+ When using the split `actions/cache/save@v4` and `actions/cache/restore@v4` actions
22
+ separately (rather than the combined `actions/cache@v4`), the `path:` value in the
23
+ restore step must exactly match the `path:` value used in the save step — including
24
+ relative vs absolute paths, trailing slashes, and directory names.
25
+
26
+ If the paths differ even slightly, the cache entry is stored under a hash that
27
+ incorporates the path value. The restore step computes a different hash and reports
28
+ "Cache not found for input keys" — identical to the message shown when the cache key
29
+ itself is wrong. Nothing in the error output indicates that a path mismatch is the
30
+ actual cause, making this extremely difficult to debug.
31
+
32
+ This is an intentional implementation detail of the split actions: the combined
33
+ `actions/cache@v4` avoids this because the same `path:` string is used for both
34
+ operations automatically. Developers migrating from the combined action to split
35
+ save/restore for more granular control frequently encounter this.
36
+
37
+ Common path mismatch pitfalls:
38
+ - Save uses `./node_modules`, restore uses `node_modules` (leading `./` difference)
39
+ - Save uses `/home/runner/.cache/pip`, restore uses `~/.cache/pip`
40
+ - Save uses a trailing slash `dist/`, restore uses `dist`
41
+ - Save in one job uses a relative path that resolves differently in another job
42
+ fix: |
43
+ Ensure the `path:` value in `actions/cache/restore@v4` is the exact same string
44
+ as used in the corresponding `actions/cache/save@v4` step. Copy-paste the value
45
+ rather than retyping it to prevent subtle differences.
46
+
47
+ When using the split actions across different jobs, prefer absolute paths or paths
48
+ anchored from a stable workspace directory. Alternatively, use the combined
49
+ `actions/cache@v4` with `save-always: true` to avoid managing save/restore separately.
50
+ fix_code:
51
+ - language: yaml
52
+ label: "Identical path values in split save and restore steps"
53
+ code: |
54
+ jobs:
55
+ build:
56
+ runs-on: ubuntu-latest
57
+ steps:
58
+ - uses: actions/checkout@v4
59
+
60
+ - name: Restore cached build output
61
+ uses: actions/cache/restore@v4
62
+ id: cache-restore
63
+ with:
64
+ path: .cache/build-output # Must match save step below exactly
65
+ key: build-${{ github.sha }}
66
+ restore-keys: build-
67
+
68
+ - name: Build
69
+ if: steps.cache-restore.outputs.cache-hit != 'true'
70
+ run: npm run build
71
+
72
+ - name: Save build cache
73
+ if: steps.cache-restore.outputs.cache-hit != 'true'
74
+ uses: actions/cache/save@v4
75
+ with:
76
+ path: .cache/build-output # Copy-pasted from restore step — no differences
77
+ key: build-${{ github.sha }}
78
+
79
+ - language: yaml
80
+ label: "Combined cache action avoids path mismatch entirely"
81
+ code: |
82
+ jobs:
83
+ build:
84
+ runs-on: ubuntu-latest
85
+ steps:
86
+ - uses: actions/checkout@v4
87
+
88
+ - name: Cache build output
89
+ uses: actions/cache@v4
90
+ with:
91
+ path: .cache/build-output
92
+ key: build-${{ github.sha }}
93
+ restore-keys: build-
94
+ save-always: true # Always saves even when cache-hit is true
95
+
96
+ - run: npm run build
97
+ prevention:
98
+ - "Copy-paste the path value between save and restore steps — never retype it"
99
+ - "Use the combined actions/cache@v4 with save-always: true unless you specifically need split save/restore behavior"
100
+ - "Prefer absolute paths (e.g., /home/runner/.cache/pip) over relative paths when save and restore are in different jobs"
101
+ - "When debugging cache misses, add a run: echo '${{ toJSON(steps.cache.outputs) }}' step to inspect what the cache lookup returned"
102
+ docs:
103
+ - url: "https://github.com/actions/cache/blob/main/save/README.md"
104
+ label: "actions/cache/save — README"
105
+ - url: "https://github.com/actions/cache/blob/main/restore/README.md"
106
+ label: "actions/cache/restore — README"
107
+ - url: "https://github.com/actions/cache/issues/1444"
108
+ label: "GitHub Issue #1444 — Path mismatch causes misleading cache miss error"
109
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows"
110
+ label: "GitHub Docs — Caching dependencies to speed up workflows"
@@ -0,0 +1,104 @@
1
+ id: concurrency-timing-033
2
+ title: "Matrix jobs silently cancel each other when concurrency group key excludes matrix dimension values"
3
+ category: concurrency-timing
4
+ severity: silent-failure
5
+ tags:
6
+ - concurrency
7
+ - matrix
8
+ - cancel-in-progress
9
+ - silent-failure
10
+ - matrix-strategy
11
+ patterns:
12
+ - regex: 'This run was cancelled\.'
13
+ flags: 'i'
14
+ - regex: 'The operation was cancelled\.'
15
+ flags: 'i'
16
+ error_messages:
17
+ - "This run was cancelled."
18
+ - "The operation was cancelled."
19
+ root_cause: |
20
+ When a workflow defines a `concurrency` group that does not include any matrix-specific
21
+ expression values, all matrix jobs resolve to the exact same concurrency group key.
22
+
23
+ With `cancel-in-progress: true`, each queued matrix job cancels the previously queued
24
+ or running job with the same key. Since all matrix jobs are queued nearly simultaneously,
25
+ they race for the single concurrency slot and only the last-scheduled job survives.
26
+ All others silently show "This run was cancelled." with no further explanation.
27
+
28
+ Example problematic configuration:
29
+ concurrency:
30
+ group: ${{ github.workflow }}-${{ github.ref }}
31
+ cancel-in-progress: true
32
+ strategy:
33
+ matrix:
34
+ os: [ubuntu-latest, windows-latest, macos-latest]
35
+
36
+ All three matrix jobs resolve to the same key, e.g., "test-refs/heads/main", and
37
+ cancel each other until only one remains.
38
+
39
+ Without `cancel-in-progress`, matrix jobs queue serially behind the single slot instead
40
+ of running in parallel, also incorrect and defeating the purpose of the matrix.
41
+ fix: |
42
+ Include at least one matrix dimension value in the concurrency group key so each matrix
43
+ job gets a unique group name and can run in parallel without cancelling each other.
44
+
45
+ Place `concurrency:` at the job level and reference the matrix variable:
46
+
47
+ jobs:
48
+ test:
49
+ concurrency:
50
+ group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.os }}
51
+ cancel-in-progress: true
52
+
53
+ If the goal is to cancel ALL previous matrix jobs when a new push arrives, apply
54
+ workflow-level concurrency with a key unique to the entire run (e.g., github.sha),
55
+ but note this serializes matrix sets rather than running jobs in parallel.
56
+ fix_code:
57
+ - language: yaml
58
+ label: "Job-level concurrency includes matrix.os — each OS runs in its own slot"
59
+ code: |
60
+ jobs:
61
+ test:
62
+ runs-on: ${{ matrix.os }}
63
+ concurrency:
64
+ group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.os }}
65
+ cancel-in-progress: true
66
+ strategy:
67
+ fail-fast: false
68
+ matrix:
69
+ os: [ubuntu-latest, windows-latest, macos-latest]
70
+ steps:
71
+ - uses: actions/checkout@v4
72
+ - run: npm ci && npm test
73
+ - language: yaml
74
+ label: "Multi-dimension matrix — include all axis values in group key"
75
+ code: |
76
+ jobs:
77
+ test:
78
+ runs-on: ubuntu-latest
79
+ concurrency:
80
+ group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.node }}-${{ matrix.os }}
81
+ cancel-in-progress: true
82
+ strategy:
83
+ fail-fast: false
84
+ matrix:
85
+ node: ['18', '20', '22']
86
+ os: [ubuntu-latest, windows-latest]
87
+ steps:
88
+ - uses: actions/checkout@v4
89
+ - uses: actions/setup-node@v4
90
+ with:
91
+ node-version: ${{ matrix.node }}
92
+ - run: npm test
93
+ prevention:
94
+ - "Always include at least one matrix dimension variable in the concurrency group key when combining matrix strategy with concurrency"
95
+ - "Use job-level concurrency (under jobs.<name>:) rather than workflow-level when matrix is involved, for per-job slot control"
96
+ - "Review the Actions UI — if matrix jobs show 'This run was cancelled' without an explicit failure, check for concurrency group key collisions"
97
+ - "Run actionlint on your workflow files to catch common concurrency and expression issues"
98
+ docs:
99
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/control-the-concurrency-of-workflows-and-jobs"
100
+ label: "GitHub Docs — Controlling the concurrency of workflows and jobs"
101
+ - url: "https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs"
102
+ label: "GitHub Docs — Using a matrix for your jobs"
103
+ - url: "https://github.com/orgs/community/discussions/24616"
104
+ label: "GitHub Community — Matrix jobs cancelled unexpectedly with concurrency groups"
@@ -0,0 +1,123 @@
1
+ id: concurrency-timing-034
2
+ title: "Job-level cancel-in-progress only cancels that job — sibling jobs from the same run continue executing"
3
+ category: concurrency-timing
4
+ severity: silent-failure
5
+ tags:
6
+ - concurrency
7
+ - cancel-in-progress
8
+ - job-level
9
+ - workflow-level
10
+ - deployment
11
+ - silent-failure
12
+ patterns:
13
+ - regex: 'This run was cancelled\.'
14
+ flags: 'i'
15
+ - regex: 'cancel-in-progress.*true'
16
+ flags: 'i'
17
+ error_messages:
18
+ - "This run was cancelled."
19
+ root_cause: |
20
+ When `concurrency:` is placed at the job level (under `jobs.<job-id>:`) rather than at
21
+ the workflow level (top-level key), cancellation scope is limited to that single job only.
22
+ Other jobs in the same workflow run — whether still queued, in-progress, or already
23
+ completed — are not affected by the job-level concurrency signal.
24
+
25
+ Developers commonly place `concurrency:` under a single job (e.g., `deploy`) while
26
+ expecting it to cancel the entire previous workflow run. The result is a dangerous race:
27
+
28
+ Run 1: build (succeeded) → deploy (running, concurrency group "deploy-prod")
29
+ Run 2: build (starts) → deploy (queued, same group)
30
+
31
+ Run 2's deploy job fires cancel-in-progress and cancels Run 1's deploy job — but only
32
+ if Run 1's deploy is still in-progress. If Run 1's build was slow and deploy hasn't
33
+ started yet, or if both runs' builds overlap, the serialization guarantee breaks down.
34
+
35
+ More critically: if `concurrency:` is absent from the build job, Run 1's build and
36
+ Run 2's build run simultaneously. Run 1's build (old code) may complete and trigger
37
+ Run 1's deploy AFTER Run 2's deploy has already deployed new code, producing a
38
+ rollback to old code with no warning.
39
+
40
+ Workflow-level concurrency cancels the ENTIRE run (all jobs). Job-level concurrency
41
+ cancels only THAT job. This distinction is frequently misunderstood.
42
+ fix: |
43
+ Place `concurrency:` at the workflow level (top-level) to cancel the entire previous
44
+ workflow run — all jobs — when a new run starts. Use job-level concurrency only when
45
+ you deliberately want different cancellation granularity per job.
46
+
47
+ For CI/CD where the entire pipeline should be replaced by the latest push:
48
+ concurrency:
49
+ group: ${{ github.workflow }}-${{ github.ref }}
50
+ cancel-in-progress: true
51
+ jobs:
52
+ build: ...
53
+ deploy: ...
54
+
55
+ For serializing deploys while allowing parallel builds:
56
+ jobs:
57
+ build:
58
+ # No concurrency key — builds run in parallel
59
+ deploy:
60
+ needs: build
61
+ concurrency:
62
+ group: deploy-${{ github.ref }}
63
+ cancel-in-progress: false # Queue, do not cancel in-progress deployments
64
+ fix_code:
65
+ - language: yaml
66
+ label: "Workflow-level concurrency cancels entire run including all jobs"
67
+ code: |
68
+ name: CI/CD Pipeline
69
+
70
+ on:
71
+ push:
72
+ branches: [main]
73
+
74
+ # Top-level: cancels ALL jobs in the previous workflow run on new push
75
+ concurrency:
76
+ group: ${{ github.workflow }}-${{ github.ref }}
77
+ cancel-in-progress: true
78
+
79
+ jobs:
80
+ build:
81
+ runs-on: ubuntu-latest
82
+ steps:
83
+ - uses: actions/checkout@v4
84
+ - run: npm ci && npm run build
85
+
86
+ deploy:
87
+ needs: build
88
+ runs-on: ubuntu-latest
89
+ environment: production
90
+ steps:
91
+ - run: ./scripts/deploy.sh
92
+ - language: yaml
93
+ label: "Job-level concurrency for deploy serialization only (intentional pattern)"
94
+ code: |
95
+ jobs:
96
+ build:
97
+ runs-on: ubuntu-latest
98
+ # No concurrency key — parallel builds are fine
99
+ steps:
100
+ - uses: actions/checkout@v4
101
+ - run: npm ci && npm run build
102
+
103
+ deploy:
104
+ needs: build
105
+ runs-on: ubuntu-latest
106
+ # Job-level: serialize deploys, but never cancel an in-progress deployment
107
+ concurrency:
108
+ group: deploy-${{ github.ref }}
109
+ cancel-in-progress: false
110
+ steps:
111
+ - run: ./scripts/deploy.sh
112
+ prevention:
113
+ - "Use workflow-level concurrency (top-level key, not under a job) when the entire pipeline should be replaced by the latest push"
114
+ - "Use job-level concurrency only when you intentionally want different cancellation behavior per job"
115
+ - "Set cancel-in-progress: false for deploy jobs — cancelling an in-progress deployment can leave infrastructure in a partially updated state"
116
+ - "Add concurrency to EVERY job in a pipeline if using job-level groups, not just the deploy job"
117
+ docs:
118
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/control-the-concurrency-of-workflows-and-jobs"
119
+ label: "GitHub Docs — Controlling the concurrency of workflows and jobs"
120
+ - url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#concurrency"
121
+ label: "GitHub Actions workflow syntax — concurrency"
122
+ - url: "https://github.com/orgs/community/discussions/57540"
123
+ label: "GitHub Community — Job-level vs workflow-level concurrency scoping"
@@ -0,0 +1,124 @@
1
+ id: known-unsolved-037
2
+ title: "Skipped jobs show as 'skipped' — branch protection required status checks block PR merge"
3
+ category: known-unsolved
4
+ severity: limitation
5
+ tags:
6
+ - required-status-checks
7
+ - branch-protection
8
+ - skipped
9
+ - conditional-job
10
+ - merge-blocked
11
+ - known-limitation
12
+ patterns:
13
+ - regex: 'if:\s+github\.event_name\s+!=\s+'
14
+ flags: i
15
+ - regex: 'if:\s+\$\{\{\s*github\.event_name\s*!=\s*'
16
+ flags: i
17
+ error_messages:
18
+ - "Required status check 'ci/test' was not successful — status: skipped"
19
+ - "Branch protection rule requires passing status checks before merging"
20
+ root_cause: |
21
+ When a job's if: condition evaluates to false, GitHub Actions marks that job as
22
+ "Skipped" in the Checks API. Branch protection rules that require a specific check
23
+ to pass only accept a conclusion of "success" — not "skipped", "cancelled", or
24
+ "neutral".
25
+
26
+ This creates a practical trap: a team sets up a required check for a job like
27
+ ci/test, then later adds an if: condition to skip that job on certain events
28
+ (for example, to avoid running tests on tag pushes or documentation-only changes).
29
+ The conditional logic is correct for workflow execution, but the skipped conclusion
30
+ permanently blocks all PRs where that condition evaluates to false.
31
+
32
+ GitHub has documented this as intentional: a skipped job is not a successful job.
33
+ There is no configuration option to treat "skipped" as equivalent to "success" for
34
+ branch protection purposes as of 2026. The workaround requires a structural change
35
+ to the workflow.
36
+ fix: |
37
+ This is a known limitation with no direct fix. The recommended workarounds are:
38
+
39
+ 1. ALWAYS-PASS WRAPPER JOB: Create a wrapper job that always runs and reports
40
+ the real outcome. The wrapper job becomes the required status check instead of
41
+ the real CI job. If CI was skipped (e.g., docs-only change), the wrapper passes.
42
+ If CI ran and failed, the wrapper fails.
43
+
44
+ 2. MOVE THE CONDITION INSIDE THE JOB: Instead of using if: at the job level, keep
45
+ the job always running and use if: at the step level. An empty job always
46
+ succeeds, so the required status check passes.
47
+
48
+ 3. PATHS FILTER PATTERN (dorny/paths-filter): Use a separate filtering job and
49
+ pass its output as a condition parameter to downstream jobs while keeping a
50
+ pass-through job for status check reporting.
51
+
52
+ 4. RE-EVALUATE THE REQUIRED CHECK: If the check is truly optional for some events,
53
+ consider removing it from required status checks and instead enforce it only at
54
+ the merge queue level.
55
+ fix_code:
56
+ - language: yaml
57
+ label: "Problem: conditional job is skipped, blocking required status check"
58
+ code: |
59
+ on: [push, pull_request]
60
+
61
+ jobs:
62
+ test:
63
+ runs-on: ubuntu-latest
64
+ # This job is required in branch protection, but gets skipped on push to main
65
+ if: github.event_name == 'pull_request'
66
+ steps:
67
+ - run: npm test
68
+ # On push events, this job is SKIPPED, blocking required status check
69
+ - language: yaml
70
+ label: "Fix: always-pass wrapper job becomes the required status check"
71
+ code: |
72
+ on: [push, pull_request]
73
+
74
+ jobs:
75
+ test:
76
+ runs-on: ubuntu-latest
77
+ if: github.event_name == 'pull_request'
78
+ steps:
79
+ - uses: actions/checkout@v4
80
+ - run: npm test
81
+
82
+ # Set THIS job as the required status check in branch protection
83
+ ci-status:
84
+ runs-on: ubuntu-latest
85
+ needs: [test]
86
+ if: always()
87
+ steps:
88
+ - name: Report CI result
89
+ run: |
90
+ result="${{ needs.test.result }}"
91
+ if [[ "$result" == "success" || "$result" == "skipped" ]]; then
92
+ echo "CI passed or was intentionally skipped — OK to merge"
93
+ else
94
+ echo "CI failed (result: $result)"
95
+ exit 1
96
+ fi
97
+ - language: yaml
98
+ label: "Alternative: move condition inside job so job always runs and passes when empty"
99
+ code: |
100
+ on: [push, pull_request]
101
+
102
+ jobs:
103
+ test:
104
+ runs-on: ubuntu-latest
105
+ # No if: at job level — job always runs and always produces a conclusion
106
+ steps:
107
+ - uses: actions/checkout@v4
108
+
109
+ - name: Run tests (pull_request only)
110
+ if: github.event_name == 'pull_request'
111
+ run: npm test
112
+ # If step is skipped, job still succeeds — required check passes
113
+ prevention:
114
+ - "Never add an if: condition to a job that is a required status check — use a wrapper job instead"
115
+ - "Use the always-pass wrapper pattern from the start when adding new required status checks"
116
+ - "Test required check behavior by creating a draft PR that intentionally triggers the skip condition"
117
+ - "Document which jobs are required status checks in a comment at the top of the workflow file"
118
+ docs:
119
+ - url: "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/troubleshooting-required-status-checks"
120
+ label: "GitHub Docs: Troubleshooting required status checks"
121
+ - url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idif"
122
+ label: "GitHub Docs: Job-level if conditions"
123
+ - url: "https://github.com/dorny/paths-filter"
124
+ label: "dorny/paths-filter: Conditional execution with status reporting"
@@ -0,0 +1,124 @@
1
+ id: known-unsolved-038
2
+ title: "workflow_dispatch 'Run workflow' button invisible until workflow file merges to default branch"
3
+ category: known-unsolved
4
+ severity: limitation
5
+ tags:
6
+ - workflow-dispatch
7
+ - ui
8
+ - default-branch
9
+ - known-limitation
10
+ - feature-branch
11
+ - testing
12
+ patterns:
13
+ - regex: 'run workflow.*not.*appear'
14
+ flags: 'i'
15
+ - regex: 'workflow_dispatch.*button.*missing'
16
+ flags: 'i'
17
+ - regex: 'workflow.*not.*present.*default.*branch'
18
+ flags: 'i'
19
+ error_messages:
20
+ - "This workflow has a workflow_dispatch event trigger, however a workflow_dispatch event workflow run cannot be created as this workflow is not present in the default branch."
21
+ root_cause: |
22
+ The "Run workflow" button in the GitHub Actions UI only appears when the workflow file
23
+ containing `on.workflow_dispatch` exists on the repository's default branch (e.g., main).
24
+
25
+ When you add a new `on.workflow_dispatch` workflow to a feature branch:
26
+ - The workflow file exists in the branch and can be referenced
27
+ - The "Run workflow" button does NOT appear in the GitHub Actions UI for that branch
28
+ - The GitHub REST API returns an error if you attempt to dispatch it
29
+
30
+ The REST API error message is:
31
+ "This workflow has a workflow_dispatch event trigger, however a workflow_dispatch event
32
+ workflow run cannot be created as this workflow is not present in the default branch."
33
+
34
+ This creates a catch-22: you want to validate the workflow before merging, but the trigger
35
+ mechanism (UI button and REST API dispatch) only activates after merging to the default branch.
36
+
37
+ The limitation applies consistently across:
38
+ - GitHub web UI "Run workflow" button
39
+ - REST API create_workflow_dispatch endpoint
40
+ - GitHub CLI: gh workflow run (requires workflow on default branch)
41
+
42
+ Contrast this with `on.push` and `on.pull_request` which fire correctly from any branch
43
+ that contains the workflow file.
44
+ fix: |
45
+ There is no way to trigger workflow_dispatch from the GitHub UI or standard API before
46
+ the workflow file reaches the default branch. Use these workarounds:
47
+
48
+ Option 1 (recommended): Add `on.push` temporarily during development alongside
49
+ `on.workflow_dispatch`. Push triggers fire from any branch. Remove push trigger before
50
+ merging to avoid unintended production runs.
51
+
52
+ Option 2: Use `act` (nektos/act) locally to simulate workflow_dispatch with custom inputs
53
+ before pushing to GitHub:
54
+ act workflow_dispatch -e event.json
55
+
56
+ Option 3: Merge the workflow file to a long-lived integration branch first, use it as a
57
+ staging default, then promote to main after validation.
58
+
59
+ Option 4: Commit the workflow directly to the default branch with a no-op body (echo "test"),
60
+ validate the dispatch mechanism works, then iterate in place on the default branch.
61
+
62
+ Note: Using gh api with a specific ref pointing to a branch that has the file does NOT
63
+ bypass the default-branch requirement for workflow_dispatch. The REST API enforces this
64
+ server-side regardless of ref parameter.
65
+ fix_code:
66
+ - language: yaml
67
+ label: "Temporary on.push alongside workflow_dispatch for feature-branch testing"
68
+ code: |
69
+ on:
70
+ # Production trigger (only works after merge to default branch)
71
+ workflow_dispatch:
72
+ inputs:
73
+ environment:
74
+ description: 'Target environment'
75
+ required: true
76
+ type: choice
77
+ options: [staging, production]
78
+
79
+ # DEVELOPMENT ONLY: allows testing from feature branch via push
80
+ # Remove this push trigger before merging to main
81
+ push:
82
+ branches: ['feature/my-new-dispatch-workflow']
83
+
84
+ jobs:
85
+ deploy:
86
+ runs-on: ubuntu-latest
87
+ steps:
88
+ - uses: actions/checkout@v4
89
+ - name: Deploy
90
+ env:
91
+ TARGET: ${{ inputs.environment || 'staging' }}
92
+ run: ./scripts/deploy.sh "$TARGET"
93
+ - language: yaml
94
+ label: "Minimal no-op workflow — merge first, iterate after to unlock dispatch button"
95
+ code: |
96
+ # Step 1: Merge this minimal workflow to main first to unlock the Run Workflow button
97
+ # Step 2: Iterate on the job steps on main or in a branch
98
+ on:
99
+ workflow_dispatch:
100
+ inputs:
101
+ dry_run:
102
+ description: 'Dry run mode'
103
+ type: boolean
104
+ default: true
105
+
106
+ jobs:
107
+ placeholder:
108
+ runs-on: ubuntu-latest
109
+ steps:
110
+ - run: echo "Workflow dispatch confirmed working. Replace this with real steps."
111
+ prevention:
112
+ - "When creating new workflow_dispatch workflows, plan to merge a minimal skeleton to main first before adding full logic"
113
+ - "Add on.push with a branch filter during development of any workflow_dispatch workflow — remove before merging"
114
+ - "Use act locally (nektos/act) to test workflow_dispatch input handling before pushing"
115
+ - "Document in the PR description that the Run Workflow button will appear only after merge"
116
+ docs:
117
+ - url: "https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch"
118
+ label: "GitHub Docs — workflow_dispatch event"
119
+ - url: "https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event"
120
+ label: "GitHub REST API — Create a workflow dispatch event"
121
+ - url: "https://github.com/orgs/community/discussions/24357"
122
+ label: "GitHub Community — workflow_dispatch button not showing in feature branch"
123
+ - url: "https://github.com/nektos/act"
124
+ label: "nektos/act — Run Actions locally including workflow_dispatch simulation"