@htekdev/actions-debugger 1.0.99 → 1.0.101

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,136 @@
1
+ id: permissions-auth-059
2
+ title: 'GITHUB_TOKEN expires after 24 hours on self-hosted runners — long-running jobs fail with "GITHUB_TOKEN has expired"'
3
+ category: permissions-auth
4
+ severity: error
5
+ tags:
6
+ - GITHUB_TOKEN
7
+ - token-expiry
8
+ - self-hosted-runner
9
+ - long-running-jobs
10
+ - 24-hour-limit
11
+ - authentication
12
+ patterns:
13
+ - regex: 'GITHUB_TOKEN has expired'
14
+ flags: 'i'
15
+ - regex: 'Unable to extend GITHUB_TOKEN expiration'
16
+ flags: 'i'
17
+ - regex: 'token.*expired.*self.hosted'
18
+ flags: 'i'
19
+ error_messages:
20
+ - "Unable to extend GITHUB_TOKEN expiration time due to: GITHUB_TOKEN has expired."
21
+ - "Error: fatal: unable to access 'https://github.com/...': The requested URL returned error: 403"
22
+ root_cause: |
23
+ `GITHUB_TOKEN` is an installation access token issued at the START of each job. Its
24
+ lifetime is tied to the job execution, with a hard cap determined by the runner type:
25
+
26
+ - **GitHub-hosted runners**: `GITHUB_TOKEN` lives for up to 6 hours (matching the maximum
27
+ job execution time). On hosted runners this limit is never the issue because the job
28
+ itself can't run longer than 6 hours.
29
+
30
+ - **Self-hosted runners**: Jobs can run for up to 5 days, but `GITHUB_TOKEN` can only be
31
+ refreshed for up to **24 hours**. If a job on a self-hosted runner exceeds 24 hours of
32
+ runtime, any subsequent GitHub API call, `git push`, `gh` CLI invocation, or action that
33
+ uses `${{ github.token }}` or `${{ secrets.GITHUB_TOKEN }}` will fail with a 401/403
34
+ authentication error.
35
+
36
+ The specific error message is:
37
+ "Unable to extend GITHUB_TOKEN expiration time due to: GITHUB_TOKEN has expired."
38
+
39
+ This is particularly common in:
40
+ - Large test suites or build pipelines that process massive monorepos
41
+ - ML/data pipelines that process large datasets sequentially
42
+ - Long-running deployment or migration jobs that need GitHub API access at the end
43
+ - Jobs with retries that collectively exceed 24 hours
44
+
45
+ Note: `actions/create-github-app-token` GitHub App tokens have a similar but shorter
46
+ 1-hour expiry — see permissions-auth-046 for that pattern.
47
+ fix: |
48
+ Several approaches, in order of recommendation:
49
+
50
+ 1. **Split the job into smaller jobs** — Break the long-running job into multiple shorter
51
+ jobs connected by `needs:` dependencies. Each job gets its own fresh `GITHUB_TOKEN`.
52
+
53
+ 2. **Use a GitHub App token** — Use `actions/create-github-app-token` to generate tokens
54
+ mid-job as needed, or generate a fresh token at the point in the job where you need it.
55
+
56
+ 3. **Use a PAT (Personal Access Token)** — Store a long-lived PAT in repository or
57
+ organization secrets and use it in place of `GITHUB_TOKEN` for the API calls at the
58
+ end of the long-running job. PATs do not expire in 24 hours (they expire based on the
59
+ PAT expiration date you set). Drawback: PATs are tied to a specific user account.
60
+
61
+ 4. **Use a machine user PAT** — Create a dedicated machine account and use its PAT.
62
+ This decouples the secret from any individual developer's account.
63
+ fix_code:
64
+ - language: yaml
65
+ label: 'Problem: long-running self-hosted job uses GITHUB_TOKEN after 24+ hours'
66
+ code: |
67
+ jobs:
68
+ long-build:
69
+ runs-on: [self-hosted, large-runner]
70
+ steps:
71
+ - uses: actions/checkout@v4
72
+ - name: Run 26-hour data processing
73
+ run: ./scripts/process_all_data.sh # takes ~26 hours
74
+
75
+ # FAILS: GITHUB_TOKEN has expired by the time this step runs
76
+ - name: Upload results to GitHub
77
+ env:
78
+ GH_TOKEN: ${{ github.token }}
79
+ run: gh release upload v1.0 ./output/*.tar.gz
80
+ - language: yaml
81
+ label: 'Fix: split into jobs so each gets a fresh GITHUB_TOKEN'
82
+ code: |
83
+ jobs:
84
+ process-data:
85
+ runs-on: [self-hosted, large-runner]
86
+ steps:
87
+ - uses: actions/checkout@v4
88
+ - run: ./scripts/process_all_data.sh
89
+ - uses: actions/upload-artifact@v4
90
+ with:
91
+ name: output-files
92
+ path: ./output/
93
+
94
+ upload-release:
95
+ needs: process-data
96
+ runs-on: ubuntu-latest # hosted runner with fresh token
97
+ permissions:
98
+ contents: write
99
+ steps:
100
+ - uses: actions/download-artifact@v4
101
+ with:
102
+ name: output-files
103
+ path: ./output/
104
+ # GITHUB_TOKEN is fresh — just issued for this new job
105
+ - name: Upload to release
106
+ env:
107
+ GH_TOKEN: ${{ github.token }}
108
+ run: gh release upload v1.0 ./output/*.tar.gz
109
+ - language: yaml
110
+ label: 'Fix: use stored PAT when splitting jobs is not feasible'
111
+ code: |
112
+ jobs:
113
+ long-build:
114
+ runs-on: [self-hosted, large-runner]
115
+ steps:
116
+ - uses: actions/checkout@v4
117
+ - run: ./scripts/process_all_data.sh
118
+
119
+ # Use a PAT stored in secrets — does not expire in 24 hours
120
+ - name: Upload results (using PAT)
121
+ env:
122
+ GH_TOKEN: ${{ secrets.MACHINE_USER_PAT }} # PAT, not github.token
123
+ run: gh release upload v1.0 ./output/*.tar.gz
124
+ prevention:
125
+ - "Design self-hosted runner jobs to complete within 24 hours, or split them into smaller jobs connected by `needs:`."
126
+ - "If a job genuinely requires >24 hours of runtime, use a PAT or GitHub App token for API calls instead of `GITHUB_TOKEN`."
127
+ - "Add a monitoring step that logs elapsed job time — alert if a job approaches 20+ hours."
128
+ - "Avoid using `GITHUB_TOKEN` for API calls near the end of jobs that are known to run close to the 24-hour limit."
129
+ - "For GitHub-hosted runners this is not an issue — the job execution limit (6 hours) is shorter than the token lifetime."
130
+ docs:
131
+ - url: 'https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#about-the-github_token-secret'
132
+ label: 'GitHub Docs: GITHUB_TOKEN — effective maximum lifetime'
133
+ - url: 'https://stackoverflow.com/questions/75602556/how-can-i-use-a-github-token-for-more-than-24-hours'
134
+ label: 'Stack Overflow: How to use GITHUB_TOKEN for more than 24 hours'
135
+ - url: 'https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#usage-limits'
136
+ label: 'GitHub Docs: Self-hosted runner usage limits (5-day job limit)'
@@ -0,0 +1,115 @@
1
+ id: permissions-auth-060
2
+ title: 'Fork Pull Request Workflows Cannot Access Repository Secrets — Secrets Context Is Empty'
3
+ category: permissions-auth
4
+ severity: silent-failure
5
+ tags:
6
+ - fork
7
+ - pull-request
8
+ - secrets
9
+ - security
10
+ - workflow-permissions
11
+ patterns:
12
+ - regex: 'secrets\.[\w_]+.*empty|secrets context.*fork'
13
+ flags: 'i'
14
+ error_messages:
15
+ - "Error: Input required and not supplied: token"
16
+ - "HttpError: Bad credentials"
17
+ - "Error: Resource not accessible by integration"
18
+ root_cause: |
19
+ GitHub's security model prevents workflows triggered by `pull_request` events from
20
+ fork repositories from accessing repository secrets. This applies to ALL non-Dependabot
21
+ external contributor forks — not just Dependabot.
22
+
23
+ When a fork PR workflow runs:
24
+ - All `secrets.*` values resolve to empty string `''`
25
+ - `secrets.GITHUB_TOKEN` is replaced with a read-only token scoped only to the fork
26
+ repository (cannot write to the upstream repo, cannot access packages, etc.)
27
+ - Environment secrets are also unavailable
28
+ - Fine-grained PATs stored as secrets are unavailable
29
+
30
+ This is intentional — allowing forks to read secrets would enable malicious PRs to
31
+ exfiltrate credentials.
32
+
33
+ Common failure patterns:
34
+ - `actions/setup-node` with `registry-url` + `NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}` → 401 Unauthorized
35
+ - `docker/login-action` with `${{ secrets.DOCKERHUB_TOKEN }}` → login fails silently, image push 403
36
+ - `aws-actions/configure-aws-credentials` with role ARN from secrets → empty role, OIDC fallback
37
+ - Custom actions reading a `token:` input wired to `${{ secrets.MY_TOKEN }}` → action receives ''
38
+ and may throw "Input required and not supplied: token"
39
+
40
+ The fork PR restriction also applies to `workflow_dispatch` when triggered from a fork,
41
+ and to `check_suite`/`check_run` events from fork PRs.
42
+
43
+ Note: `pull_request_target` DOES have access to secrets because it runs in the context
44
+ of the BASE repository — but this introduces a different security risk (running untrusted
45
+ code with secret access) that requires careful mitigation.
46
+ fix: |
47
+ For CI checks that don't need secrets (lint, unit tests, build validation), no change
48
+ is needed — the read-only GITHUB_TOKEN is sufficient.
49
+
50
+ For steps that require secrets:
51
+
52
+ 1. Gate secret-requiring steps on `github.event.pull_request.head.repo.fork == false`
53
+ to skip them on fork PRs gracefully.
54
+ 2. Use `pull_request_target` + a separate privileged job for publishing/deploying,
55
+ but ALWAYS check out from `github.event.pull_request.head.sha` explicitly and
56
+ never run untrusted code in the same job.
57
+ 3. For package publishing, only publish from base branch pushes (not from PRs at all).
58
+ fix_code:
59
+ - language: yaml
60
+ label: "Skip secret-requiring steps on fork PRs gracefully"
61
+ code: |
62
+ jobs:
63
+ build:
64
+ runs-on: ubuntu-latest
65
+ steps:
66
+ - uses: actions/checkout@v4
67
+
68
+ - name: Run tests (works on fork PRs)
69
+ run: npm test
70
+
71
+ - name: Publish coverage report (skip on fork PRs)
72
+ if: github.event.pull_request.head.repo.fork == false
73
+ env:
74
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
75
+ run: npx codecov
76
+ - language: yaml
77
+ label: "Split fork-safe CI from privileged deployment using workflow_run"
78
+ code: |
79
+ # workflow: ci.yml — runs on all PRs including forks (no secrets needed)
80
+ on:
81
+ pull_request:
82
+ jobs:
83
+ test:
84
+ runs-on: ubuntu-latest
85
+ steps:
86
+ - uses: actions/checkout@v4
87
+ - run: npm ci && npm test
88
+
89
+ ---
90
+ # workflow: publish-coverage.yml — runs after ci.yml completes (has secrets)
91
+ on:
92
+ workflow_run:
93
+ workflows: ['CI']
94
+ types: [completed]
95
+ jobs:
96
+ coverage:
97
+ if: github.event.workflow_run.conclusion == 'success'
98
+ runs-on: ubuntu-latest
99
+ steps:
100
+ - name: Publish coverage
101
+ env:
102
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
103
+ run: echo "Publishing coverage for run ${{ github.event.workflow_run.id }}"
104
+ prevention:
105
+ - "Design CI workflows to not require secrets for the build/test steps — secrets should only be needed for publish/deploy"
106
+ - "Check `github.event.pull_request.head.repo.fork` before any step that uses secrets to provide a clear skip message"
107
+ - "Do not use `pull_request_target` as a shortcut to get secrets — it runs untrusted fork code with base repo privileges, creating a severe injection risk"
108
+ - "Use `workflow_run` to chain a privileged follow-up workflow that runs in base repo context after fork CI succeeds"
109
+ docs:
110
+ - url: "https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections"
111
+ label: "GitHub Docs — Security hardening: fork PR and secret access"
112
+ - url: "https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request"
113
+ label: "GitHub Docs — pull_request event: fork limitations"
114
+ - url: "https://securitylab.github.com/research/github-actions-preventing-pwn-requests/"
115
+ label: "GitHub Security Lab — Preventing pwn requests (pull_request_target risks)"
@@ -0,0 +1,88 @@
1
+ id: runner-environment-169
2
+ title: 'actions/runner 2.320.0+ container image removes SSH — "error: cannot run ssh: No such file or directory"'
3
+ category: runner-environment
4
+ severity: error
5
+ tags:
6
+ - runner
7
+ - ssh
8
+ - openssh-client
9
+ - git-submodules
10
+ - self-hosted
11
+ - container-runner
12
+ - 2.320
13
+ patterns:
14
+ - regex: 'error: cannot run ssh: No such file or directory'
15
+ flags: 'i'
16
+ - regex: 'fatal: unable to fork'
17
+ flags: 'i'
18
+ - regex: 'Unable to locate executable file: ssh'
19
+ flags: 'i'
20
+ - regex: 'error downloading.*ssh://.*No such file or directory'
21
+ flags: 'i'
22
+ error_messages:
23
+ - "error: cannot run ssh: No such file or directory"
24
+ - "fatal: unable to fork"
25
+ - "Unable to locate executable file: ssh. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable."
26
+ - "error downloading 'ssh://git@github.com/...': /usr/bin/git exited with 128: error: cannot run ssh: No such file or directory"
27
+ root_cause: |
28
+ Starting with actions/runner 2.320.0, the base container image
29
+ (ghcr.io/actions/actions-runner) no longer ships openssh-client by default.
30
+ Workflows that rely on git operations over SSH — including submodule cloning
31
+ via SSH URLs, Terragrunt module downloads over ssh://, or any step that
32
+ calls ssh-keyscan — fail at runtime because the ssh binary is absent from
33
+ the runner's PATH.
34
+
35
+ This affected both self-hosted runners built from the official container
36
+ image and any ephemeral runners (Actions Runner Controller / ARC) that
37
+ derive from ghcr.io/actions/actions-runner:2.320.0+. Hosted runners
38
+ (ubuntu-latest, etc.) were not affected because they use a separate,
39
+ pre-loaded VM image that still includes openssh-client.
40
+
41
+ The regression was introduced when the container image was slimmed down
42
+ between 2.319.1 and 2.320.0 without a corresponding changelog callout,
43
+ leaving self-hosted container runners silently broken on upgrade.
44
+ fix: |
45
+ Install openssh-client in the runner container image before the runner
46
+ process starts. For Dockerfiles extending the official image, add an
47
+ explicit RUN instruction. For ARC runner deployments, add an
48
+ initContainers step or a containerStartupCommand to install the package.
49
+
50
+ If you do not control the image, add an installation step at the top of
51
+ the failing workflow job before any SSH-dependent steps.
52
+ fix_code:
53
+ - language: yaml
54
+ label: 'Option A — Add install step in the workflow before any SSH steps'
55
+ code: |
56
+ jobs:
57
+ build:
58
+ runs-on: self-hosted
59
+ steps:
60
+ - name: Install SSH client
61
+ run: |
62
+ apt-get update -qq && apt-get install -y --no-install-recommends openssh-client
63
+ - name: Checkout with submodules
64
+ uses: actions/checkout@v4
65
+ with:
66
+ submodules: recursive
67
+ ssh-key: ${{ secrets.SSH_KEY }}
68
+ - language: dockerfile
69
+ label: 'Option B — Bake openssh-client into a custom runner image'
70
+ code: |
71
+ FROM ghcr.io/actions/actions-runner:latest
72
+ USER root
73
+ RUN apt-get update && apt-get install -y --no-install-recommends openssh-client && rm -rf /var/lib/apt/lists/*
74
+ USER runner
75
+ prevention:
76
+ - "Pin your ARC / self-hosted runner image to a tested version (e.g., ghcr.io/actions/actions-runner:2.319.1) and test upgrades in a staging environment before rolling out."
77
+ - "Add a pre-flight check step that runs 'which ssh || (apt-get install -y openssh-client)' if your workflow requires SSH."
78
+ - "Prefer HTTPS-based submodule URLs and GITHUB_TOKEN for submodule auth in GitHub Actions — this avoids SSH entirely."
79
+ - "Subscribe to the actions/runner GitHub Releases feed to catch breaking changes in container image composition."
80
+ docs:
81
+ - url: 'https://github.com/actions/runner/issues/3490'
82
+ label: 'actions/runner #3490 — Runner 2.320.0 no longer has SSH installed'
83
+ - url: 'https://github.com/actions/runner/issues/3488'
84
+ label: 'actions/runner #3488 — Runner version 2.320.0 breaks custom image'
85
+ - url: 'https://github.com/actions/checkout/issues/1942'
86
+ label: 'actions/checkout #1942 — Unable to locate executable file: ssh'
87
+ - url: 'https://github.com/actions/runner/releases'
88
+ label: 'actions/runner releases'
@@ -0,0 +1,93 @@
1
+ id: runner-environment-170
2
+ title: 'windows-latest Migration to Windows Server 2025 Removes MSVC v142 (VS 2019) Toolchain'
3
+ category: runner-environment
4
+ severity: error
5
+ tags:
6
+ - windows
7
+ - msvc
8
+ - cmake
9
+ - msbuild
10
+ - toolchain
11
+ - migration
12
+ patterns:
13
+ - regex: 'MSB8020.*v142.*cannot be found'
14
+ flags: 'i'
15
+ - regex: 'build tools for v142.*Platform Toolset.*v142.*cannot be found'
16
+ flags: 'i'
17
+ - regex: 'CMAKE_GENERATOR_TOOLSET.*v142.*not found'
18
+ flags: 'i'
19
+ error_messages:
20
+ - "MSBUILD : error MSB8020: The build tools for v142 (Platform Toolset = 'v142') cannot be found."
21
+ - "CMake Error: The CMAKE_CXX_COMPILER: cl.exe is not able to compile a simple test program."
22
+ - "error MSB8020: The build tools for v142 (Platform Toolset = 'v142') cannot be found."
23
+ root_cause: |
24
+ Starting November 2024, `windows-latest` was migrated to Windows Server 2025 with
25
+ Visual Studio 2022 17.12 as the default build environment. Windows Server 2025 runner
26
+ images do not include the Visual Studio 2019 build tools (MSVC v142 / toolset 14.2).
27
+
28
+ Projects that explicitly request the VS 2019 toolchain via any of these mechanisms fail:
29
+ - MSBuild `<PlatformToolset>v142</PlatformToolset>` in .vcxproj files
30
+ - CMake `-T v142` flag or `set(CMAKE_GENERATOR_TOOLSET v142)` in CMakeLists.txt
31
+ - MSBuild command-line flag `/p:PlatformToolset=v142`
32
+ - Visual Studio solution files pinned to VS 2019 (toolset version 142)
33
+
34
+ Windows Server 2022 runners (`windows-2022`) continue to offer both VS 2019
35
+ (v142) and VS 2022 (v143) toolchains in parallel. After the `windows-latest`
36
+ migration, workflows that did not pin to `windows-2022` and relied on v142
37
+ break silently on runners already migrated while appearing to work in older
38
+ runner pools.
39
+
40
+ The migration timeline:
41
+ - Nov 2024: windows-latest begins pointing to Windows Server 2025 (partial rollout)
42
+ - Early 2025: Full rollout; all new `windows-latest` queue jobs use WS 2025
43
+
44
+ Reference: https://github.com/actions/runner-images/issues/10751
45
+ fix: |
46
+ Choose one of the following approaches:
47
+
48
+ 1. Upgrade to the v143 (VS 2022) toolchain — recommended for long-term compatibility.
49
+ 2. Pin to `windows-2022` runner if you cannot migrate immediately.
50
+ 3. Install the VS 2019 build tools component manually (slow, increases job time).
51
+ fix_code:
52
+ - language: yaml
53
+ label: "Option A: upgrade toolchain to v143 (VS 2022) in CMakeLists.txt"
54
+ code: |
55
+ # In CMakeLists.txt — remove explicit toolset pinning
56
+ # cmake_minimum_required(VERSION 3.20)
57
+ # project(MyProject)
58
+ # Previously had: set(CMAKE_GENERATOR_TOOLSET "v142")
59
+ # Remove or update to:
60
+ # set(CMAKE_GENERATOR_TOOLSET "v143") # or omit to use default
61
+ - language: yaml
62
+ label: "Option B: pin to windows-2022 to keep v142 support"
63
+ code: |
64
+ jobs:
65
+ build:
66
+ runs-on: windows-2022 # has both v142 and v143 available
67
+ steps:
68
+ - uses: actions/checkout@v4
69
+ - name: Build with CMake
70
+ run: cmake -B build -T v142 && cmake --build build
71
+ - language: yaml
72
+ label: "Option C: install VS 2019 build tools on windows-latest (slow)"
73
+ code: |
74
+ jobs:
75
+ build:
76
+ runs-on: windows-latest
77
+ steps:
78
+ - uses: actions/checkout@v4
79
+ - name: Install VS 2019 Build Tools
80
+ run: |
81
+ choco install visualstudio2019buildtools --package-parameters "--add Microsoft.VisualStudio.Component.VC.v142.x86.x64" -y
82
+ prevention:
83
+ - "Audit all .vcxproj and CMakeLists.txt files for explicit v142 toolset references before migrating to windows-latest"
84
+ - "Pin to `windows-2022` in CI to preserve VS 2019 toolchain availability while planning an upgrade"
85
+ - "Watch https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-Readme.md for current pre-installed toolchain versions"
86
+ - "Use `vswhere` in a pre-build step to detect installed VS components and fail fast with a descriptive error"
87
+ docs:
88
+ - url: "https://github.com/actions/runner-images/issues/10751"
89
+ label: "runner-images #10751 — windows-latest migration to Windows Server 2025"
90
+ - url: "https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-Readme.md"
91
+ label: "Windows Server 2025 runner image README — pre-installed software"
92
+ - url: "https://learn.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-170"
93
+ label: "Microsoft Docs — CMake toolset configuration"
@@ -0,0 +1,96 @@
1
+ id: silent-failures-090
2
+ title: '`github.event.pull_request.*` Fields Are Null on Non-PR Events — Comparisons Silently Evaluate Incorrectly'
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - github-context
7
+ - pull_request
8
+ - null-context
9
+ - push-event
10
+ - multi-event
11
+ - expression
12
+ patterns:
13
+ - regex: 'github\.event\.pull_request\.\w+'
14
+ flags: 'i'
15
+ error_messages:
16
+ - "Unexpected value '' in expression"
17
+ root_cause: |
18
+ When a workflow is triggered by a non-pull_request event — such as `push`, `schedule`,
19
+ `workflow_dispatch`, `workflow_call`, or `release` — the `github.event.pull_request`
20
+ object is null. Every child field evaluates to empty string `""` in expressions.
21
+
22
+ This silently breaks conditions in multi-event workflows:
23
+
24
+ - `if: github.event.pull_request.draft == false`
25
+ On a push event, `draft` resolves to `""`. The comparison `"" == false` evaluates to
26
+ FALSE (empty string is not boolean false), so the step silently skips.
27
+
28
+ - `if: github.event.pull_request.merged == true`
29
+ Always false on push events, causing steps intended for merged-PR context to silently
30
+ never execute.
31
+
32
+ - `env: PR_NUMBER: ${{ github.event.pull_request.number }}`
33
+ Sets `PR_NUMBER` to empty string on push events. Downstream scripts that require a
34
+ PR number fail with "invalid argument" or use `0` as the number.
35
+
36
+ - `if: github.event.pull_request.head.repo.fork != true`
37
+ Always evaluates to true on push events (empty string != true), bypassing fork guards.
38
+
39
+ The root issue is that workflows triggered by multiple events (e.g., `on: [push,
40
+ pull_request]`) share the same `if:` conditions and `env:` references, but the
41
+ `github.event` object structure differs per event type. Note that `yaml-syntax-060`
42
+ covers the object filter `.*` operator on null — this entry covers field-level null
43
+ comparisons in `if:` and `env:` contexts.
44
+ fix: |
45
+ Guard all `github.event.pull_request.*` access with an event type check:
46
+
47
+ if: github.event_name == 'pull_request' && github.event.pull_request.draft == false
48
+
49
+ For env variables that should only apply on PR events, set them conditionally:
50
+ use a step to export the variable only when running under a pull_request trigger,
51
+ or use separate jobs per event type.
52
+
53
+ For the fork guard pattern, use `github.event_name == 'pull_request' &&
54
+ github.event.pull_request.head.repo.fork` as a combined check rather than relying
55
+ on the fork field being non-null.
56
+ fix_code:
57
+ - language: yaml
58
+ label: "Guard PR context access with event type check in if: condition"
59
+ code: |
60
+ jobs:
61
+ check-draft:
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - name: Skip if draft PR (only meaningful on PR events)
65
+ # Without the event_name guard, draft is "" on push — comparison silently fails
66
+ if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
67
+ run: echo "Proceeding — not a draft PR"
68
+ - language: yaml
69
+ label: "Export PR-specific context safely in multi-event workflows"
70
+ code: |
71
+ on: [push, pull_request]
72
+
73
+ jobs:
74
+ deploy:
75
+ runs-on: ubuntu-latest
76
+ steps:
77
+ - name: Export PR metadata (PR events only)
78
+ if: github.event_name == 'pull_request'
79
+ run: |
80
+ echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV
81
+ echo "IS_DRAFT=${{ github.event.pull_request.draft }}" >> $GITHUB_ENV
82
+
83
+ - name: Deploy
84
+ run: |
85
+ echo "Branch: ${{ github.ref_name }}"
86
+ # Use PR_NUMBER only after confirming event_name == pull_request above
87
+ prevention:
88
+ - "In multi-event workflows, always guard `github.event.pull_request.*` access with `github.event_name == 'pull_request'`"
89
+ - "Run actionlint on workflow files — it detects context availability mismatches per event type"
90
+ - "Test workflows manually for both push and pull_request trigger types to verify that conditional logic works as expected"
91
+ - "Prefer separate jobs or separate workflow files per event type rather than a single workflow handling multiple events with shared conditions"
92
+ docs:
93
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows"
94
+ label: "GitHub Docs: Events that trigger workflows — context availability per event"
95
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#github-context"
96
+ label: "GitHub Docs: Contexts — github.event object structure"
@@ -0,0 +1,94 @@
1
+ id: silent-failures-091
2
+ title: '`github.ref_name` Returns Ephemeral Merge Ref (`123/merge`) on `pull_request` Trigger, Not Branch Name'
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - github-context
7
+ - ref-name
8
+ - pull-request
9
+ - docker
10
+ - branch-name
11
+ - deployment
12
+ patterns:
13
+ - regex: '\d+/merge'
14
+ flags: 'i'
15
+ error_messages:
16
+ - "invalid reference format"
17
+ - "invalid tag format"
18
+ root_cause: |
19
+ When a workflow is triggered by a `pull_request` event, GitHub creates an ephemeral
20
+ merge commit that merges the PR head into the base branch. The context variables for
21
+ this run reflect the merge ref, not the source branch:
22
+
23
+ - `github.ref` = `refs/pull/123/merge`
24
+ - `github.ref_name` = `123/merge`
25
+ - `github.sha` = SHA of the ephemeral merge commit
26
+ - `github.head_ref` = `feature-branch-name` (the actual PR branch)
27
+ - `github.base_ref` = `main` (the target branch)
28
+
29
+ Using `github.ref_name` in contexts that expect a branch name silently produces
30
+ the merge ref string `123/merge` instead:
31
+
32
+ - **Docker image tags**: `docker.io/myapp:123/merge` — Docker rejects the forward slash
33
+ as an invalid tag character (`invalid reference format` error), OR worse, interprets
34
+ `123` as a registry name.
35
+ - **Deployment environment names**: The environment is created as `123/merge` rather
36
+ than the branch name, causing routing rules to miss the deployment.
37
+ - **Branch-based conditional logic**: `if: github.ref_name == 'feature-foo'` always
38
+ evaluates to false on pull_request events.
39
+ - **Artifact naming**: Artifacts uploaded with the ref_name contain forward slashes,
40
+ causing path traversal issues on some download handlers.
41
+
42
+ `github.head_ref` is only populated for `pull_request` and `pull_request_target`
43
+ events, making branch detection logic that works on push but silently fails on PR
44
+ events harder to diagnose.
45
+ fix: |
46
+ Use the correct context variable for each event type:
47
+
48
+ - For the PR's source branch name: `github.head_ref` (only set on pull_request events)
49
+ - For push events' branch name: `github.ref_name`
50
+ - For a branch name that works across both event types, use a conditional expression.
51
+ fix_code:
52
+ - language: yaml
53
+ label: "Unified branch name across push and pull_request events"
54
+ code: |
55
+ jobs:
56
+ build:
57
+ runs-on: ubuntu-latest
58
+ steps:
59
+ - name: Determine branch name
60
+ id: branch
61
+ run: |
62
+ if [ "${{ github.event_name }}" = "pull_request" ]; then
63
+ echo "name=${{ github.head_ref }}" >> "$GITHUB_OUTPUT"
64
+ else
65
+ echo "name=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
66
+ fi
67
+
68
+ - name: Build and tag Docker image
69
+ run: |
70
+ # Sanitize slashes for use in image tags
71
+ TAG=$(echo "${{ steps.branch.outputs.name }}" | tr '/' '-')
72
+ docker build -t "myapp:${TAG}" .
73
+ - language: yaml
74
+ label: "Expression-based branch name (no shell step needed)"
75
+ code: |
76
+ env:
77
+ BRANCH_NAME: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}
78
+
79
+ jobs:
80
+ deploy:
81
+ runs-on: ubuntu-latest
82
+ environment: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}
83
+ steps:
84
+ - run: echo "Deploying branch ${{ env.BRANCH_NAME }}"
85
+ prevention:
86
+ - "Never use `github.ref_name` directly in Docker image tags — always sanitize forward slashes with `tr '/' '-'` or similar"
87
+ - "Test branch-detection expressions with both `push` and `pull_request` triggers in a dry-run workflow"
88
+ - "Use `github.head_ref` for PR branch name and `github.ref_name` for push branch name — they are NOT interchangeable"
89
+ - "Add an explicit check: if `github.ref_name` contains a `/`, the workflow is running on a pull_request or merge ref, not a regular branch"
90
+ docs:
91
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#github-context"
92
+ label: "GitHub Docs — github context reference"
93
+ - url: "https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request"
94
+ label: "GitHub Docs — pull_request event"