@htekdev/actions-debugger 1.0.46 → 1.0.48

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,97 @@
1
+ id: concurrency-timing-028
2
+ title: "Matrix job outputs use last-writer-wins — only the final completing matrix instance's value is visible to downstream jobs"
3
+ category: concurrency-timing
4
+ severity: silent-failure
5
+ tags:
6
+ - matrix
7
+ - outputs
8
+ - last-writer-wins
9
+ - job-outputs
10
+ - needs
11
+ - aggregation
12
+ patterns:
13
+ - regex: 'jobs\.\w+\.outputs\.\w+'
14
+ flags: "i"
15
+ - regex: 'matrix.*output.*empty|matrix.*output.*missing|output.*only.*one.*matrix'
16
+ flags: "i"
17
+ error_messages:
18
+ - "Job output contains only one matrix item's value despite all matrix jobs completing successfully"
19
+ - "Downstream job receives empty string from needs.<job>.outputs.<name> after matrix build"
20
+ root_cause: |
21
+ When a matrix strategy job writes to GITHUB_OUTPUT, all parallel matrix instances share
22
+ the same output key namespace keyed at the logical job ID level in the workflow DAG.
23
+ Multiple matrix runners mapping to one job ID race to write the same output keys. The
24
+ last matrix runner to flush its output to the Actions service wins — all prior values for
25
+ that key are silently overwritten.
26
+
27
+ GitHub Docs state explicitly: "The value of the output is the last value set by the
28
+ expression that evaluates the output." There is no merge, append, or aggregation of
29
+ outputs across matrix instances.
30
+
31
+ Common manifestations:
32
+ - A release matrix collecting artifact paths — the downstream job sees only one path
33
+ - A test matrix emitting pass/fail status per OS — only the last-completing OS result survives
34
+ - A build matrix producing version or hash strings — final output value is non-deterministic
35
+ and varies by runner scheduling order, making the issue intermittent and hard to reproduce
36
+
37
+ This is an intentional platform constraint. The job outputs map is a flat key-value store
38
+ keyed at the job level, not the matrix-instance level. It cannot store per-instance values.
39
+ fix: |
40
+ Aggregate matrix results via uniquely-named artifacts rather than job outputs.
41
+
42
+ Strategy: each matrix instance uploads its result as a named artifact that includes the
43
+ matrix dimension in the artifact name (required by actions/upload-artifact@v4 anyway, which
44
+ rejects duplicate names). The fan-in dependent job downloads all artifacts and processes
45
+ them together.
46
+
47
+ For simple pass/fail aggregation, rely on the default needs: behavior — a job with
48
+ needs: [build] only starts when ALL matrix instances of build have completed successfully.
49
+ No custom output collection is needed for overall-success gating.
50
+ fix_code:
51
+ - language: yaml
52
+ label: "Aggregate matrix results via uniquely-named artifacts (recommended pattern)"
53
+ code: |
54
+ jobs:
55
+ build:
56
+ strategy:
57
+ matrix:
58
+ os: [ubuntu-latest, windows-latest, macos-latest]
59
+ runs-on: ${{ matrix.os }}
60
+ steps:
61
+ - uses: actions/checkout@v4
62
+
63
+ - name: Build and capture result
64
+ run: echo "artifact-for-${{ matrix.os }}" > result.txt
65
+
66
+ - name: Upload per-matrix artifact
67
+ uses: actions/upload-artifact@v4
68
+ with:
69
+ # Matrix dimension in name prevents v4 duplicate-name error
70
+ name: build-result-${{ matrix.os }}
71
+ path: result.txt
72
+
73
+ release:
74
+ needs: build # Waits for ALL matrix instances to succeed
75
+ runs-on: ubuntu-latest
76
+ steps:
77
+ - name: Download all matrix results
78
+ uses: actions/download-artifact@v4
79
+ with:
80
+ pattern: build-result-*
81
+ merge-multiple: true
82
+ path: results/
83
+
84
+ - name: Process aggregated results
85
+ run: ls results/ && cat results/result.txt
86
+ prevention:
87
+ - "Never rely on GITHUB_OUTPUT in matrix jobs to pass per-instance values to downstream jobs"
88
+ - "Always include the matrix dimension in artifact names when uploading from matrix jobs"
89
+ - "Use needs: [job] to gate on all matrix instances succeeding — no custom output collection needed for pass/fail"
90
+ - "Add a workflow comment documenting that the fan-in pattern is required for multi-value matrix aggregation"
91
+ docs:
92
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs"
93
+ label: "GitHub Docs: Passing information between jobs"
94
+ - url: "https://github.com/orgs/community/discussions/26286"
95
+ label: "GitHub Community #26286: Matrix job outputs — last writer wins"
96
+ - url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idoutputs"
97
+ label: "GitHub Docs: jobs.<job_id>.outputs"
@@ -0,0 +1,92 @@
1
+ id: concurrency-timing-029
2
+ title: "cancel-in-progress: true does not release a deployment environment lock — subsequent runs wait indefinitely"
3
+ category: concurrency-timing
4
+ severity: error
5
+ tags:
6
+ - concurrency
7
+ - cancel-in-progress
8
+ - deployment-environment
9
+ - environment-lock
10
+ - queue-stall
11
+ - protection-rules
12
+ patterns:
13
+ - regex: 'waiting.*deployment.*environment|environment.*lock.*not.*released'
14
+ flags: "i"
15
+ - regex: 'deployment.*stuck.*waiting|cancel.*in.*progress.*environment.*stall'
16
+ flags: "i"
17
+ error_messages:
18
+ - "Deployment stuck in 'Waiting for deployment' state after previous run was cancelled"
19
+ - "Job has been waiting for a deployment to complete for over N minutes"
20
+ root_cause: |
21
+ GitHub Actions deployment environments use a distributed lock to ensure only one workflow
22
+ run deploys to an environment at a time. When a job is cancelled while holding or waiting
23
+ for an environment lock, the lock release is not guaranteed to be atomic with the cancellation.
24
+
25
+ The race condition sequence:
26
+ 1. Run A acquires the environment lock and begins deploying
27
+ 2. Run B starts, enters "Waiting for deployment" state (environment locked by A)
28
+ 3. Run C triggers with cancel-in-progress: true — Run B (the waiting run) is cancelled
29
+ 4. Run A completes and releases its lock
30
+ 5. Run C's deploy job is now queued but sees the environment still showing a pending entry
31
+ from the cancelled Run B, causing it to wait indefinitely
32
+
33
+ A separate scenario: Run A itself is cancelled mid-deploy. The environment can enter
34
+ a "deployment in progress" limbo state where no active run owns the lock but the
35
+ environment page still shows a pending deployment. New runs queue successfully but
36
+ hang waiting for a lock that will never be released automatically.
37
+
38
+ This is exacerbated on active repos where pushes arrive faster than deploy jobs complete,
39
+ causing continuous queue stalls on the environment.
40
+ fix: |
41
+ Separate the concurrency group into two distinct layers: one for build/test jobs
42
+ (safe to cancel freely) and one for deploy jobs (must serialize to completion).
43
+
44
+ Build jobs use cancel-in-progress: true — wasteful test runs are discarded when
45
+ a newer commit arrives. Deploy jobs use cancel-in-progress: false — they run to
46
+ completion serially, preventing mid-deploy cancellations that create lock limbo.
47
+
48
+ Set timeout-minutes on all deploy jobs so stuck environment waits self-resolve with
49
+ a clear failure rather than hanging for the job's default 6-hour timeout.
50
+
51
+ To manually recover a stalled environment: navigate to Settings → Environments →
52
+ select the stalled environment → cancel any pending deployment entries in the UI.
53
+ fix_code:
54
+ - language: yaml
55
+ label: "Separate concurrency groups — cancel builds freely, serialize deploys safely"
56
+ code: |
57
+ jobs:
58
+ build:
59
+ # Cancel in-progress builds freely — no environment lock at stake
60
+ concurrency:
61
+ group: build-${{ github.ref }}
62
+ cancel-in-progress: true
63
+ runs-on: ubuntu-latest
64
+ steps:
65
+ - uses: actions/checkout@v4
66
+ - run: echo "build and test steps"
67
+
68
+ deploy:
69
+ needs: build
70
+ environment: production
71
+ # NEVER cancel in-progress deploy jobs — let them finish to avoid lock limbo
72
+ concurrency:
73
+ group: deploy-production
74
+ cancel-in-progress: false
75
+ timeout-minutes: 30 # Self-heal on stuck environment wait (default is 6h)
76
+ runs-on: ubuntu-latest
77
+ steps:
78
+ - uses: actions/checkout@v4
79
+ - run: echo "deploy steps here"
80
+ prevention:
81
+ - "Never use cancel-in-progress: true on jobs that target a deployment environment"
82
+ - "Always set timeout-minutes on deploy jobs — the default 6-hour timeout makes stalls very painful"
83
+ - "Use separate concurrency groups for build (cancel-friendly) vs deploy (serialize) stages"
84
+ - "Monitor the Environments page for stale 'Deployment pending' entries after CI incidents"
85
+ - "Consider dedicated environment concurrency groups (e.g., deploy-production vs deploy-staging) to prevent cross-environment interference"
86
+ docs:
87
+ - url: "https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-deployments/managing-environments-for-deployment"
88
+ label: "GitHub Docs: Managing environments for deployment"
89
+ - url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#concurrency"
90
+ label: "GitHub Docs: Workflow concurrency"
91
+ - url: "https://github.com/orgs/community/discussions/14490"
92
+ label: "GitHub Community #14490: cancel-in-progress and deployment environment lock"
@@ -0,0 +1,96 @@
1
+ id: permissions-auth-036
2
+ title: 'GITHUB_TOKEN cannot push commits to branches protected by "Require signed commits" — token identity cannot sign'
3
+ category: permissions-auth
4
+ severity: error
5
+ tags:
6
+ - github-token
7
+ - signed-commits
8
+ - branch-protection
9
+ - gpg
10
+ - push
11
+ - protected-branch
12
+ patterns:
13
+ - regex: 'GH006.*Protected branch.*signed|Commits must have verified signatures'
14
+ flags: 'i'
15
+ - regex: 'Changes must be signed|protected branch.*signed.*commits'
16
+ flags: 'i'
17
+ error_messages:
18
+ - 'remote: error: GH006: Protected branch update failed for refs/heads/main.'
19
+ - 'remote: error: Commits must have verified signatures.'
20
+ - 'remote: error: Changes must be signed.'
21
+ root_cause: |
22
+ GitHub's "Require signed commits" branch protection rule requires every pushed commit
23
+ to carry a verified GPG or SSH signature. The GITHUB_TOKEN is a short-lived bearer token
24
+ scoped to a workflow run — it has no associated GPG or SSH signing key and cannot produce
25
+ verified commit signatures.
26
+
27
+ This means any workflow that creates commits and pushes them using GITHUB_TOKEN will fail
28
+ on branches protected by "Require signed commits", regardless of the token's permission
29
+ level. Having contents: write permission is necessary but not sufficient: the signature
30
+ requirement is enforced by a separate push hook after permission checks pass.
31
+
32
+ Common workflow patterns that hit this:
33
+ - Auto-commit actions (stefanzweifel/auto-commit-action, EndBug/add-and-commit) that
34
+ commit generated files (changelogs, coverage badges, built assets) back to main
35
+ - Release workflows that bump version numbers in committed files before tagging
36
+ - Automated dependency update workflows that commit lockfile changes
37
+ - Documentation generation workflows that commit generated API docs or OpenAPI specs
38
+
39
+ The GH006 error appears in the push step's output but the job may not fail loudly if
40
+ the step's exit code is not checked carefully. In some auto-commit actions, the failure
41
+ surfaces as an unexpected empty push with no error surfaced to the workflow summary.
42
+ fix: |
43
+ Option 1 — Create a pull request instead of direct push (recommended): Use
44
+ peter-evans/create-pull-request or similar to push the auto-generated commit to a
45
+ feature branch, then open a PR. Feature branches are not subject to the main branch's
46
+ "Require signed commits" protection.
47
+
48
+ Option 2 — GitHub App with bypass: Create a GitHub App and add it to the branch
49
+ protection "Allow specific actors to bypass required pull request reviews and required
50
+ status checks" bypass list. Generate tokens for the App in the workflow and push with
51
+ the App token. The App's bot commits can bypass the signed-commits requirement if
52
+ explicitly listed as a bypass actor.
53
+
54
+ Option 3 — Signed commits via GitHub App SSH key: Configure a GitHub App with an
55
+ SSH signing key. Use the App installation token and the App's SSH key for commit
56
+ signing via GIT_COMMITTER_EMAIL and gpg.format = ssh configuration.
57
+
58
+ The GITHUB_TOKEN itself cannot be configured for commit signing — this is a platform
59
+ limitation with no in-token workaround.
60
+ fix_code:
61
+ - language: yaml
62
+ label: 'Create a pull request instead of pushing directly — avoids signed commits requirement on main'
63
+ code: |
64
+ jobs:
65
+ update-generated:
66
+ runs-on: ubuntu-latest
67
+ permissions:
68
+ contents: write
69
+ pull-requests: write
70
+ steps:
71
+ - uses: actions/checkout@v4
72
+
73
+ - name: Regenerate files
74
+ run: make generate
75
+
76
+ - name: Create pull request for generated output
77
+ uses: peter-evans/create-pull-request@v7
78
+ with:
79
+ commit-message: 'chore: regenerate auto-generated files'
80
+ branch: automated/regenerate-output
81
+ title: 'chore: automated file regeneration'
82
+ body: 'Automated regeneration triggered by CI. Merge after review.'
83
+ # Commit targets the feature branch, not main — signed-commits rule on
84
+ # main does not apply to the automated/ branch
85
+ prevention:
86
+ - 'Before enabling "Require signed commits" on a branch, audit all workflows that push commits using GITHUB_TOKEN to that branch'
87
+ - 'Use the create-pull-request pattern for auto-generated commits rather than pushing directly to protected branches'
88
+ - 'Document in workflow files that GITHUB_TOKEN cannot push to branches with signed-commits protection'
89
+ - 'If a GitHub App bypass is used, rotate the App private key regularly and scope permissions to the minimum required'
90
+ docs:
91
+ - url: 'https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-signed-commits'
92
+ label: 'GitHub Docs: Require signed commits branch protection'
93
+ - url: 'https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification'
94
+ label: 'GitHub Docs: About commit signature verification'
95
+ - url: 'https://github.com/orgs/community/discussions/13836'
96
+ label: 'GitHub Community #13836: GITHUB_TOKEN cannot push to branch requiring signed commits'
@@ -0,0 +1,100 @@
1
+ id: runner-environment-103
2
+ title: "actions/setup-go cache skipped with 'no file matched go.sum' when go.sum is absent or not at workspace root"
3
+ category: runner-environment
4
+ severity: warning
5
+ tags:
6
+ - setup-go
7
+ - go
8
+ - cache
9
+ - go-sum
10
+ - monorepo
11
+ - cache-miss
12
+ - cache-dependency-path
13
+ patterns:
14
+ - regex: 'No file.*matched.*go\.sum|go\.sum.*not.*found.*cache'
15
+ flags: 'i'
16
+ - regex: 'setup-go.*cache.*skip|Unable to find.*go\.sum'
17
+ flags: 'i'
18
+ error_messages:
19
+ - 'Warning: No file in /home/runner/work/repo/repo matched to [**/go.sum, !**/vendor/**], make sure you have checked out the target repository'
20
+ - 'go.sum not found, module cache will not be saved'
21
+ - 'Cache miss — no go.sum file located at expected path'
22
+ root_cause: |
23
+ actions/setup-go uses go.sum as the cache key hash source. When cache: true (the default
24
+ since setup-go@v4), the action runs hashFiles('**/go.sum') to compute the cache key.
25
+ If no go.sum file is found anywhere in the workspace, the action emits a warning and
26
+ silently skips saving and restoring the module cache entirely — the job continues with
27
+ exit 0 but all Go dependencies are downloaded from scratch on every run.
28
+
29
+ Common root causes:
30
+
31
+ 1. New project: go mod tidy has not been run — go.sum does not yet exist in the repo.
32
+
33
+ 2. Monorepo layout: Go modules live under subdirectories (e.g., services/api/go.sum).
34
+ The default glob **/go.sum should find nested files, but when combined with the
35
+ !**/vendor/** exclusion or when the repo structure is non-standard, detection can fail.
36
+
37
+ 3. Go workspace (go.work): modules are organized under subdirectories managed by a
38
+ go.work file; individual go.sum files may be present but not at the expected root.
39
+
40
+ 4. Ordering issue: actions/setup-go runs before actions/checkout, so go.sum is not
41
+ yet in the workspace when the cache key is computed.
42
+
43
+ 5. go.sum is listed in .gitignore (non-standard but seen in internal tooling repos
44
+ that regenerate dependencies on every run).
45
+
46
+ The missing-cache warning is only visible in the setup-go step's log output and does
47
+ not fail the workflow. On large Go projects, silent cache bypass wastes 60-120+ seconds
48
+ downloading modules from proxy.golang.org on every CI run.
49
+ fix: |
50
+ For monorepo or nested module layouts, explicitly set cache-dependency-path to the
51
+ location of the go.sum file(s). A glob pattern can match multiple modules if needed.
52
+
53
+ For new projects: run go mod tidy locally and commit go.sum before pushing.
54
+
55
+ For go.work workspaces: list each module's go.sum explicitly in cache-dependency-path
56
+ to ensure all modules participate in the cache key.
57
+
58
+ Always confirm actions/checkout runs before actions/setup-go in the steps list.
59
+ fix_code:
60
+ - language: yaml
61
+ label: 'Set cache-dependency-path for a nested module in a monorepo'
62
+ code: |
63
+ steps:
64
+ - uses: actions/checkout@v4
65
+
66
+ - uses: actions/setup-go@v5
67
+ with:
68
+ go-version: '1.22'
69
+ cache: true
70
+ # Explicit path overrides default glob — required when go.sum is not at repo root
71
+ cache-dependency-path: services/api/go.sum
72
+
73
+ - language: yaml
74
+ label: 'Multiple modules in a go.work workspace — list all go.sum paths'
75
+ code: |
76
+ steps:
77
+ - uses: actions/checkout@v4
78
+
79
+ - uses: actions/setup-go@v5
80
+ with:
81
+ go-version: '1.22'
82
+ cache: true
83
+ # Multi-line value: each module go.sum contributes to the aggregate cache key
84
+ cache-dependency-path: |
85
+ services/api/go.sum
86
+ services/worker/go.sum
87
+ pkg/shared/go.sum
88
+ prevention:
89
+ - 'Always run go mod tidy before the first commit to ensure go.sum exists in the repository'
90
+ - 'In monorepos, explicitly set cache-dependency-path rather than relying on the default **/go.sum glob'
91
+ - 'Check the setup-go step output for "No file matched" warnings — caching may be silently disabled without failing the job'
92
+ - 'Ensure actions/checkout runs before actions/setup-go so go.sum is present when the cache key is computed'
93
+ - 'Never add go.sum to .gitignore — it is required for reproducible builds and module cache keying'
94
+ docs:
95
+ - url: 'https://github.com/actions/setup-go#caching-dependency-files-and-build-outputs'
96
+ label: 'actions/setup-go: Caching dependency files and build outputs'
97
+ - url: 'https://github.com/actions/setup-go/issues/289'
98
+ label: 'setup-go #289: Cache fails when go.sum not at repository root'
99
+ - url: 'https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-go'
100
+ label: 'GitHub Docs: Building and testing Go'
@@ -0,0 +1,107 @@
1
+ id: silent-failures-051
2
+ title: 'environment.url expression evaluates to empty string when referencing steps.*.outputs — resolved before steps run'
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - environment
7
+ - deployment-url
8
+ - step-outputs
9
+ - expression-evaluation
10
+ - dynamic-url
11
+ - deployment-widget
12
+ patterns:
13
+ - regex: 'environment.*url.*steps\.\w+\.outputs\.\w+'
14
+ flags: 'i'
15
+ - regex: 'deployment.*url.*empty|environment.*url.*not.*set|page_url.*empty'
16
+ flags: 'i'
17
+ error_messages:
18
+ - 'Deployment environment shows no URL in GitHub UI despite environment.url referencing a step output'
19
+ - 'Deployment URL is empty string — steps.deploy.outputs.url is non-empty in the step logs'
20
+ root_cause: |
21
+ The jobs.<id>.environment.url field is evaluated at job scheduling time — before ANY steps
22
+ in the job have run and before any step outputs exist. Expressions that reference
23
+ steps.*.outputs.* from the same job silently resolve to empty string. GitHub does not
24
+ emit a warning or error when this occurs.
25
+
26
+ This is a fundamental evaluation ordering constraint: the environment block (both name and
27
+ url) is part of the workflow plan GitHub evaluates once when the job is queued. The job
28
+ has not yet started, so step outputs are undefined and resolve to empty.
29
+
30
+ Contexts SAFE to use in environment.url (available at scheduling time):
31
+ - github.* — static for the run
32
+ - vars.* — repository/org/environment variables
33
+ - inputs.* — workflow_dispatch or workflow_call inputs
34
+ - needs.<prior-job>.outputs.* — outputs from already-completed upstream jobs
35
+ - env.* declared at the job level
36
+
37
+ Contexts that silently resolve to empty in environment.url:
38
+ - steps.*.outputs.* — step has not run yet
39
+ - steps.*.conclusion / steps.*.outcome — step has not run yet
40
+ - Any runtime-computed value from within the same job
41
+
42
+ Common scenario: a deploy step creates a preview URL (e.g., Vercel preview, Heroku review
43
+ app, AWS CloudFormation output), writes it to GITHUB_OUTPUT, and the developer tries to
44
+ surface it via environment.url so it appears in the GitHub deployment widget on the PR.
45
+ The widget shows no URL, and no error is ever raised.
46
+ fix: |
47
+ Route the dynamic deployment URL through job outputs of the deploy job, then reference
48
+ needs.deploy.outputs.<name> in a dependent job's environment.url. The needs context IS
49
+ available at scheduling time for downstream jobs because the upstream job has already
50
+ completed.
51
+
52
+ For cases where the URL can be computed entirely from pre-execution contexts (github.ref_name,
53
+ inputs.*, vars.*), use a static URL expression directly in environment.url without
54
+ any step output dependency.
55
+ fix_code:
56
+ - language: yaml
57
+ label: 'Two-job pattern — expose deployment URL as job output, reference via needs in downstream job'
58
+ code: |
59
+ jobs:
60
+ deploy:
61
+ runs-on: ubuntu-latest
62
+ outputs:
63
+ # Promote step output to job output — available to downstream jobs via needs
64
+ app_url: ${{ steps.deploy.outputs.url }}
65
+ steps:
66
+ - uses: actions/checkout@v4
67
+ - name: Deploy
68
+ id: deploy
69
+ run: |
70
+ DEPLOY_URL="https://preview-${{ github.run_id }}.example.com"
71
+ echo "url=${DEPLOY_URL}" >> $GITHUB_OUTPUT
72
+
73
+ link-deployment:
74
+ needs: deploy
75
+ runs-on: ubuntu-latest
76
+ environment:
77
+ name: preview
78
+ # needs.deploy.outputs.* resolves correctly — deploy job already completed
79
+ url: ${{ needs.deploy.outputs.app_url }}
80
+ steps:
81
+ - run: echo "Linked deployment to ${{ needs.deploy.outputs.app_url }}"
82
+
83
+ - language: yaml
84
+ label: 'Static URL pattern using pre-execution context (no step output dependency)'
85
+ code: |
86
+ jobs:
87
+ deploy:
88
+ runs-on: ubuntu-latest
89
+ environment:
90
+ name: preview
91
+ # github.ref_name is available at scheduling time — always resolves correctly
92
+ url: 'https://preview-${{ github.ref_name }}.example.com'
93
+ steps:
94
+ - uses: actions/checkout@v4
95
+ - run: echo "deploying..."
96
+ prevention:
97
+ - 'Never reference steps.*.outputs.* directly in environment.url — promote to a job output and reference via needs in a downstream job instead'
98
+ - 'Test environment.url by checking the deployment widget on a pull request — an empty URL indicates the expression evaluated to empty string at scheduling time'
99
+ - 'Document the two-job deploy-then-link pattern in team workflow templates to prevent rediscovery'
100
+ - 'If the URL requires a step output, always add an outputs: block to the job and reference it via needs.<job>.outputs.<name> in a downstream job'
101
+ docs:
102
+ - url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idenvironment'
103
+ label: 'GitHub Docs: jobs.<job_id>.environment syntax'
104
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs'
105
+ label: 'GitHub Docs: Passing information between jobs'
106
+ - url: 'https://github.com/orgs/community/discussions/9366'
107
+ label: 'GitHub Community #9366: Dynamic environment URL from step output resolves to empty'
@@ -0,0 +1,87 @@
1
+ id: triggers-034
2
+ title: "New workflow file added in a PR branch does not trigger — workflows only activate after merging to the default branch"
3
+ category: triggers
4
+ severity: limitation
5
+ tags:
6
+ - triggers
7
+ - new-workflow
8
+ - pr-branch
9
+ - default-branch
10
+ - known-limitation
11
+ - onboarding
12
+ patterns:
13
+ - regex: 'workflow.*not.*trigger.*pull.request|new.*workflow.*file.*no.*run'
14
+ flags: "i"
15
+ - regex: '\.github.workflows.*pr.*not.*running|workflow.*added.*branch.*not.*firing'
16
+ flags: "i"
17
+ error_messages:
18
+ - "New workflow file added in PR branch produces no workflow runs — the Actions tab shows nothing for the PR"
19
+ - "Workflow YAML exists on PR branch but no jobs are queued for push or pull_request events"
20
+ root_cause: |
21
+ GitHub Actions only executes workflow files that exist on the repository's default
22
+ branch (usually main or master). When a new workflow YAML file is added inside a
23
+ pull request branch, GitHub does NOT execute that workflow for any event on that
24
+ branch — not for the push that added the file, not for subsequent pushes to the branch,
25
+ and not for the pull_request event generated when the PR was opened.
26
+
27
+ This is an intentional security boundary. Executing arbitrary workflow files from
28
+ unreviewed feature branches would allow any contributor to inject CI code that runs
29
+ with write permissions before a maintainer has reviewed it.
30
+
31
+ GitHub's documentation states: "A workflow is triggered by an event that occurs in the
32
+ repository where the workflow file lives." The key constraint is that "lives" means on
33
+ the default branch — not just anywhere in the repository's file tree.
34
+
35
+ Consequences:
36
+ - A PR that adds the first CI workflow for a repo produces zero CI runs
37
+ - A PR adding a new required status check workflow blocks itself forever (the check
38
+ never posts because the workflow doesn't exist on the default branch)
39
+ - New workflow features cannot be validated via CI during the PR review itself
40
+ fix: |
41
+ To test a new workflow file before merging:
42
+ 1. Temporarily push just the workflow file directly to the default branch (main/master)
43
+ 2. Manually trigger it via workflow_dispatch from the Actions tab to verify it runs correctly
44
+ 3. Then open (or reopen) the feature PR — the workflow now exists on the default branch
45
+ and will run against the PR
46
+
47
+ For local testing before any push: use nektos/act to run the workflow locally against
48
+ your branch checkout. This validates the YAML and execution logic without needing the
49
+ file on the default branch.
50
+
51
+ For required status checks: never add a branch protection required status check for a
52
+ workflow that does not yet exist on the default branch — the check will never post a
53
+ result and the PR will be permanently blocked with no path to merge.
54
+ fix_code:
55
+ - language: yaml
56
+ label: "Always include workflow_dispatch to enable manual testing immediately after the first merge"
57
+ code: |
58
+ # New workflow — add workflow_dispatch so it is testable the moment it lands on main
59
+ name: CI
60
+
61
+ on:
62
+ push:
63
+ branches: [main]
64
+ pull_request:
65
+ branches: [main]
66
+ workflow_dispatch: # Allows manual trigger from the Actions tab once merged
67
+
68
+ jobs:
69
+ test:
70
+ runs-on: ubuntu-latest
71
+ steps:
72
+ - uses: actions/checkout@v4
73
+ - name: Run tests
74
+ run: echo "workflow is now active on default branch"
75
+ prevention:
76
+ - "Merge a minimal 'smoke test' version of the workflow to the default branch first to activate it, then expand in the feature PR"
77
+ - "Include workflow_dispatch in all new workflows to enable immediate manual testing after the first merge"
78
+ - "Use nektos/act for local workflow testing before pushing — validates logic without needing the file on main"
79
+ - "Never add a required status check for a workflow that does not yet exist on the default branch"
80
+ - "Document in PR description that new workflows require a prior bootstrap merge to main before CI shows results"
81
+ docs:
82
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/triggering-a-workflow"
83
+ label: "GitHub Docs: Triggering a workflow"
84
+ - url: "https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections"
85
+ label: "GitHub Docs: Security hardening — script injection risks"
86
+ - url: "https://github.com/nektos/act"
87
+ label: "nektos/act — Run GitHub Actions workflows locally"
@@ -0,0 +1,114 @@
1
+ id: triggers-035
2
+ title: "push: paths: filter silently prevents workflow from running on tag pushes — tags don't modify files"
3
+ category: triggers
4
+ severity: silent-failure
5
+ tags:
6
+ - push
7
+ - paths-filter
8
+ - tags
9
+ - silent-skip
10
+ - tag-push
11
+ - release
12
+ patterns:
13
+ - regex: 'push.*tags.*paths.*not.*trigger|tag.*push.*paths.*filter.*skip'
14
+ flags: "i"
15
+ - regex: 'release.*workflow.*not.*run.*tag|on\.push.*paths.*tags.*silent'
16
+ flags: "i"
17
+ error_messages:
18
+ - "Release workflow does not trigger on tag push when push: paths: filter is present"
19
+ - "Tag v1.0.0 pushed but no workflow run created — paths filter silently blocking trigger"
20
+ root_cause: |
21
+ When a GitHub Actions push trigger includes both tags: and paths: conditions, GitHub
22
+ evaluates BOTH conditions using AND logic — the event must match the tag pattern AND
23
+ at least one file in the push must match the paths pattern. A tag push (e.g., v1.0.0)
24
+ creates a new ref pointing to an existing commit without modifying any file contents.
25
+ Because no files are changed, the paths: filter never matches, and the workflow
26
+ silently does not run.
27
+
28
+ Example trigger block that silently fails on all tag pushes:
29
+
30
+ on:
31
+ push:
32
+ branches: [main]
33
+ tags: ['v*']
34
+ paths: ['src/**', 'package.json'] # blocks ALL tag triggers — tags change no files
35
+
36
+ YAML does not support duplicate map keys, so two separate on: push: blocks are not
37
+ possible in the same workflow file. The paths: filter applies globally to every push
38
+ event evaluated by that trigger block, including tag pushes.
39
+
40
+ This is commonly introduced when developers add a paths: filter to an existing release
41
+ workflow to avoid running expensive builds on documentation commits, inadvertently
42
+ also filtering out all tag-triggered release runs. The workflow appears to work on
43
+ branch pushes (which do change files) while silently never running on tag pushes.
44
+ fix: |
45
+ Option 1 (recommended): Replace push: tags: with the release event. The release event
46
+ fires when a GitHub Release is published from a tag and has no paths filter concept.
47
+ This also provides richer release metadata (release notes, asset URLs) via github.event.
48
+
49
+ Option 2: Remove paths: from the trigger and use an if: condition on jobs to skip
50
+ non-relevant branch pushes. The job runs for all tag pushes unconditionally, and
51
+ runs for branch pushes only when the if: condition evaluates to true based on
52
+ changed file data or event type.
53
+
54
+ Option 3: Use paths-ignore: instead of paths: with an inverted pattern. This approach
55
+ still silently blocks tag pushes (same AND-logic issue), so it should only be combined
56
+ with Option 1 or 2.
57
+ fix_code:
58
+ - language: yaml
59
+ label: "Replace push: tags: with release event — no paths filter interference"
60
+ code: |
61
+ on:
62
+ push:
63
+ branches: [main]
64
+ paths:
65
+ - 'src/**'
66
+ - 'package.json'
67
+ # Use release event for tag-based release workflows
68
+ # Fires on Release published — no paths filter concept applies
69
+ release:
70
+ types: [published]
71
+
72
+ jobs:
73
+ build-and-release:
74
+ runs-on: ubuntu-latest
75
+ steps:
76
+ - uses: actions/checkout@v4
77
+ - run: echo "runs on branch push matching paths AND on release publish"
78
+ - language: yaml
79
+ label: "Remove paths: and guard branch pushes with if: condition instead"
80
+ code: |
81
+ on:
82
+ push:
83
+ branches: [main]
84
+ tags: ['v*.*.*']
85
+ # No paths: filter — use if: condition on job to skip irrelevant branch pushes
86
+
87
+ jobs:
88
+ release:
89
+ runs-on: ubuntu-latest
90
+ # Always run on tag pushes; on branch pushes only if relevant files changed
91
+ if: startsWith(github.ref, 'refs/tags/')
92
+ steps:
93
+ - uses: actions/checkout@v4
94
+ - run: echo "only runs on tag pushes"
95
+
96
+ ci:
97
+ runs-on: ubuntu-latest
98
+ # Separate job for branch-push CI with file-change guard
99
+ if: "!startsWith(github.ref, 'refs/tags/')"
100
+ steps:
101
+ - uses: actions/checkout@v4
102
+ - run: echo "only runs on branch pushes"
103
+ prevention:
104
+ - "Never mix tags: and paths: in the same push trigger block — paths filter applies to tag pushes too"
105
+ - "Use the release event instead of push: tags: for release workflows to avoid paths filter confusion"
106
+ - "After adding a paths: filter to any workflow, test by pushing a tag to verify release triggering still works"
107
+ - "Add a comment near any paths: filter warning that it will also suppress tag-push triggers in the same block"
108
+ docs:
109
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#push"
110
+ label: "GitHub Docs: push event trigger and filters"
111
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#release"
112
+ label: "GitHub Docs: release event trigger"
113
+ - url: "https://github.com/orgs/community/discussions/25597"
114
+ label: "GitHub Community #25597: paths filter prevents tag-triggered workflow runs"
@@ -0,0 +1,73 @@
1
+ id: triggers-036
2
+ title: 'branches-ignore filter does not suppress tag pushes — tags always fire unless tags-ignore is also specified'
3
+ category: triggers
4
+ severity: silent-failure
5
+ tags:
6
+ - push
7
+ - branches-ignore
8
+ - tags
9
+ - tag-push
10
+ - filter
11
+ - trigger-bypass
12
+ patterns:
13
+ - regex: 'on.*push.*branches-ignore|branches-ignore.*\*\*'
14
+ flags: 'i'
15
+ - regex: 'tag.*push.*trigger.*unexpected|branches-ignore.*tags.*still.*fire'
16
+ flags: 'i'
17
+ error_messages:
18
+ - "Workflow triggered by tag push despite branches-ignore: ['**'] — expected no branch pushes to trigger"
19
+ - 'v1.2.3 tag push fires a workflow that should only run on branch pushes'
20
+ root_cause: |
21
+ GitHub Actions push event filters (branches:, branches-ignore:, paths:, paths-ignore:) apply
22
+ only to branch-ref pushes. Tag pushes target a refs/tags/* ref, not a refs/heads/* ref, and
23
+ are entirely unaffected by branch filters.
24
+
25
+ When a workflow declares on: push with branches-ignore: but no tags: or tags-ignore: filter,
26
+ all tag pushes trigger the workflow unconditionally regardless of the branch filter.
27
+
28
+ A critical misunderstanding: adding branches-ignore: ['**'] looks like it "disables all push
29
+ triggers" but it only disables branch pushes. Tag pushes still fire unconditionally.
30
+
31
+ GitHub documentation states that push event filters are independent per ref type: branch
32
+ filters only affect refs/heads/* pushes, and tag filters only affect refs/tags/* pushes.
33
+ They do not compose across ref types — setting one does not implicitly filter the other.
34
+
35
+ Consequence: release tag pushes (v1.2.3) trigger CI and CD workflows not designed for them,
36
+ potentially running expensive test suites or deploying artifacts on every version tag.
37
+ This is among the top-voted GitHub Community questions about push event triggers and
38
+ appears repeatedly in Stack Overflow answers on the [github-actions] tag.
39
+ fix: |
40
+ Add an explicit tags-ignore: ['**'] filter to prevent all tag pushes from triggering the
41
+ workflow. Alternatively, use an explicit branches: allowlist — but note that branch
42
+ allowlists also do not suppress tag pushes; tags-ignore: is always required separately.
43
+
44
+ For workflows intended ONLY for tags (release workflows), remove branches filters entirely
45
+ and add tags: with the desired semver glob pattern.
46
+ fix_code:
47
+ - language: yaml
48
+ label: 'Add tags-ignore to explicitly suppress all tag pushes alongside branches-ignore'
49
+ code: |
50
+ on:
51
+ push:
52
+ branches-ignore:
53
+ - 'dependabot/**'
54
+ tags-ignore:
55
+ - '**' # Required — branches-ignore does NOT suppress tag pushes
56
+ - language: yaml
57
+ label: 'Release workflow — run only on semver tags, no branch triggers'
58
+ code: |
59
+ on:
60
+ push:
61
+ tags:
62
+ - 'v[0-9]+.[0-9]+.[0-9]+' # Only fires on semver tags like v1.2.3
63
+ # No branches filter — exclusive tag-only trigger
64
+ prevention:
65
+ - "When adding branches or branches-ignore filters, always decide explicitly whether tag pushes should be included or excluded — then set tags: or tags-ignore: accordingly"
66
+ - 'Use actionlint to validate push trigger filter combinations before committing'
67
+ - 'Document the intent in workflow comments: "This workflow runs on branch pushes only. Tag pushes excluded via tags-ignore."'
68
+ - 'Audit release tag workflows to confirm they use tags: filters rather than relying on implicit gaps in branch filters'
69
+ docs:
70
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#push'
71
+ label: 'GitHub Docs: push event — filter pattern cheat sheet'
72
+ - url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onpushbranchestagsbranches-ignoretags-ignore'
73
+ label: 'GitHub Docs: on.push branches/tags filter syntax'
@@ -0,0 +1,114 @@
1
+ id: yaml-syntax-035
2
+ title: "Unquoted YAML value starting with ${{ expression }} causes parse error — { triggers flow mapping syntax"
3
+ category: yaml-syntax
4
+ severity: error
5
+ tags:
6
+ - yaml
7
+ - expressions
8
+ - quoting
9
+ - parse-error
10
+ - flow-mapping
11
+ - workflow-syntax
12
+ patterns:
13
+ - regex: 'mapping values are not allowed|did not find expected key'
14
+ flags: "i"
15
+ - regex: 'invalid workflow file.*yaml|yaml.*parse.*error.*expression'
16
+ flags: "i"
17
+ error_messages:
18
+ - "Invalid workflow file: .github/workflows/workflow.yml#L12: mapping values are not allowed in this context"
19
+ - "You have an error in your yaml syntax on line X — expected a scalar value but found a mapping"
20
+ root_cause: |
21
+ The YAML specification defines { as the start of a flow mapping (an inline key-value
22
+ dictionary). GitHub Actions expressions begin with ${{ — which contains { as the second
23
+ character. When a YAML scalar value starts with ${{ without surrounding quotes, most
24
+ YAML parsers interpret the opening { as the beginning of a flow mapping and produce a
25
+ parse error.
26
+
27
+ The YAML rule: any scalar value that would be ambiguous with YAML structure characters
28
+ (including { [ : > |) must be quoted. An unquoted value starting with ${{ violates
29
+ this rule.
30
+
31
+ Affected contexts:
32
+ - env: block values: MY_VAR: ${{ secrets.TOKEN }} ← parse error
33
+ - with: block inputs: version: ${{ inputs.version }} ← parse error
34
+ - name: step names: name: ${{ matrix.os }} build ← parse error in some parsers
35
+ - run: inline shell is safe — run: is a block scalar, expressions inside are not YAML values
36
+
37
+ The if: condition is a special case. GitHub Actions parses if: fields with implicit
38
+ expression wrapping, so if: github.ref == 'refs/heads/main' works without ${{ }}.
39
+ However, if: ${{ condition }} also works when quoted. Unquoted if: ${{ ... }} may
40
+ work in permissive parsers but is not spec-compliant.
41
+
42
+ actionlint and yamllint both flag unquoted expressions as errors. GitHub's own
43
+ workflow syntax checker may silently accept some cases while rejecting others,
44
+ making the error inconsistent across different fields.
45
+ fix: |
46
+ Quote any YAML value that starts with ${{ using either single or double quotes.
47
+ Double quotes are standard; single quotes work equally well and may be preferred
48
+ when the expression contains double quote characters.
49
+
50
+ For multiline shell commands (run: | blocks), expressions inside the block scalar
51
+ do not need quoting — they are not parsed as YAML values.
52
+
53
+ For if: conditions, omit the ${{ }} wrapper entirely — GitHub Actions implicitly
54
+ wraps if: values in expression evaluation. Use: if: github.ref == 'refs/heads/main'
55
+ not if: ${{ github.ref == 'refs/heads/main' }}.
56
+ fix_code:
57
+ - language: yaml
58
+ label: "Quote YAML values starting with ${{ to prevent YAML parse errors"
59
+ code: |
60
+ # Incorrect — unquoted values starting with ${{ cause YAML parse errors
61
+ jobs:
62
+ bad:
63
+ runs-on: ubuntu-latest
64
+ env:
65
+ MY_VAR: ${{ secrets.MY_SECRET }} # parse error
66
+
67
+ # Correct — double-quoted (standard)
68
+ jobs:
69
+ good-double:
70
+ runs-on: ubuntu-latest
71
+ env:
72
+ MY_VAR: "${{ secrets.MY_SECRET }}" # safe
73
+ steps:
74
+ - uses: actions/setup-node@v4
75
+ with:
76
+ node-version: "${{ inputs.node-version }}"
77
+
78
+ # Also correct — single-quoted
79
+ jobs:
80
+ good-single:
81
+ runs-on: ubuntu-latest
82
+ env:
83
+ MY_VAR: '${{ secrets.MY_SECRET }}' # safe
84
+
85
+ # run: block scalar — no quoting needed inside |
86
+ jobs:
87
+ run-block:
88
+ runs-on: ubuntu-latest
89
+ steps:
90
+ - name: Show SHA
91
+ run: |
92
+ echo ${{ github.sha }} # safe — inside block scalar
93
+
94
+ # if: — use bare expression without ${{ }} wrapper
95
+ jobs:
96
+ guarded:
97
+ runs-on: ubuntu-latest
98
+ steps:
99
+ - name: Step only on main
100
+ if: github.ref == 'refs/heads/main' # preferred — no ${{ }} needed
101
+ run: echo "on main"
102
+ prevention:
103
+ - "Quote all YAML values that begin with ${{ — use double or single quotes consistently"
104
+ - "Install actionlint as a pre-commit hook to catch unquoted expression errors before push"
105
+ - "Use block scalar (|) for multi-line run: steps — no expression quoting needed inside block scalars"
106
+ - "For if: conditions, write bare expressions without the ${{ }} wrapper"
107
+ - "Enable GitHub's built-in YAML syntax check — push to a test branch and review the Actions tab immediately"
108
+ docs:
109
+ - url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions"
110
+ label: "GitHub Docs: Workflow syntax for GitHub Actions"
111
+ - url: "https://rhysd.github.io/actionlint/"
112
+ label: "actionlint — static checker for GitHub Actions workflow files"
113
+ - url: "https://yaml.org/spec/1.2.2/#flow-mappings"
114
+ label: "YAML 1.2 spec: Flow mappings"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@htekdev/actions-debugger",
3
- "version": "1.0.46",
3
+ "version": "1.0.48",
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",