@htekdev/actions-debugger 1.0.113 → 1.0.115

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 (34) hide show
  1. package/errors/caching-artifacts/cache-corrupt-on-cancel-during-restore-save-always.yml +136 -0
  2. package/errors/caching-artifacts/restore-keys-asterisk-literal-not-glob.yml +107 -0
  3. package/errors/concurrency-timing/concurrency-timing-053.yml +83 -0
  4. package/errors/concurrency-timing/pull-request-review-shared-concurrency-cancels-ci.yml +131 -0
  5. package/errors/known-unsolved/github-script-esm-not-supported.yml +111 -0
  6. package/errors/known-unsolved/job-outputs-string-only-no-array-object.yml +142 -0
  7. package/errors/known-unsolved/known-unsolved-062.yml +87 -0
  8. package/errors/known-unsolved/runner-rest-api-busy-false-broker-state-desync.yml +102 -0
  9. package/errors/permissions-auth/oidc-immutable-sub-claim-new-repo-trust-policy-mismatch.yml +122 -0
  10. package/errors/permissions-auth/permissions-auth-064.yml +122 -0
  11. package/errors/permissions-auth/permissions-auth-065.yml +97 -0
  12. package/errors/permissions-auth/permissions-auth-066.yml +129 -0
  13. package/errors/permissions-auth/upload-code-coverage-missing-code-quality-write-permission.yml +94 -0
  14. package/errors/runner-environment/arc-kubernetes-checkout-circular-json-container-hook.yml +101 -0
  15. package/errors/runner-environment/cache-restore-windows-runner-silent-crash.yml +130 -0
  16. package/errors/runner-environment/git-248-fetch-tags-shallow-clone-regression.yml +100 -0
  17. package/errors/runner-environment/javascript-actions-alpine-arm64-not-supported.yml +121 -0
  18. package/errors/runner-environment/runner-environment-188.yml +96 -0
  19. package/errors/runner-environment/runner-environment-191.yml +147 -0
  20. package/errors/runner-environment/runner-environment-192.yml +144 -0
  21. package/errors/runner-environment/runner-environment-193.yml +136 -0
  22. package/errors/runner-environment/runner-environment-194.yml +86 -0
  23. package/errors/runner-environment/runner-environment-199.yml +93 -0
  24. package/errors/runner-environment/setup-python-macos-self-hosted-symlink-permission-denied.yml +94 -0
  25. package/errors/runner-environment/setup-python-windows-self-hosted-no-admin-install-fails.yml +101 -0
  26. package/errors/silent-failures/checkout-v6-clean-false-deletes-workspace-on-repo-change.yml +119 -0
  27. package/errors/silent-failures/queue-max-silently-ignored-with-cancel-in-progress.yml +109 -0
  28. package/errors/silent-failures/silent-failures-102.yml +141 -0
  29. package/errors/silent-failures/silent-failures-104.yml +119 -0
  30. package/errors/triggers/triggers-069.yml +100 -0
  31. package/errors/yaml-syntax/continue-on-error-inputs-composite-action-unexpected-value.yml +110 -0
  32. package/errors/yaml-syntax/yaml-syntax-068.yml +137 -0
  33. package/errors/yaml-syntax/yaml-syntax-069.yml +118 -0
  34. package/package.json +1 -1
@@ -0,0 +1,141 @@
1
+ id: silent-failures-102
2
+ title: 'Matrix include: Property Boolean Values Coerced to Strings — Conditional Jobs Silently Misbehave'
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - matrix
7
+ - include
8
+ - boolean
9
+ - string-coercion
10
+ - if-condition
11
+ - fromJSON
12
+ patterns:
13
+ - regex: 'matrix\.[a-z_]+\s*==\s*(?:true|false)\b'
14
+ flags: 'i'
15
+ - regex: 'if:\s*\$\{\{\s*matrix\.[a-z_]+\s*\}\}'
16
+ flags: 'i'
17
+ error_messages:
18
+ - "if: ${{ matrix.enabled }}"
19
+ - "if: ${{ matrix.enabled == false }}"
20
+ - "if: ${{ matrix.deploy == true }}"
21
+ root_cause: |
22
+ All matrix property values — including those injected via `include:` entries — are coerced to
23
+ **strings** before expression evaluation at runtime. A matrix property configured as:
24
+
25
+ ```yaml
26
+ strategy:
27
+ matrix:
28
+ include:
29
+ - os: ubuntu-latest
30
+ enabled: false
31
+ - os: windows-latest
32
+ enabled: true
33
+ ```
34
+
35
+ produces `matrix.enabled` equal to the string `"false"` or `"true"`, not the boolean
36
+ `false` / `true`. This creates two distinct silent failure modes:
37
+
38
+ 1. `if: ${{ matrix.enabled }}` — the string `"false"` is **truthy** in GitHub Actions expression
39
+ syntax (non-empty string = true). The job/step **always runs** even when the intent is to skip
40
+ entries where `enabled: false`.
41
+
42
+ 2. `if: ${{ matrix.enabled == false }}` — compares a string against a boolean. In GitHub
43
+ Actions expression syntax, `"false" == false` evaluates to `false` (type mismatch). The
44
+ condition **always evaluates to false**, silently skipping every include entry regardless
45
+ of the configured value.
46
+
47
+ In both cases the workflow completes without errors or warnings. Only the observable runtime
48
+ behavior is wrong: jobs that should be skipped always run, or jobs that should run are
49
+ silently skipped.
50
+
51
+ This is distinct from composite action boolean input coercion (silent-failures-004), which
52
+ covers `inputs.*` properties. Matrix properties have no `type:` annotation — they are always
53
+ strings at expression evaluation time.
54
+ fix: |
55
+ Use `fromJSON()` to parse the matrix property string into a native boolean before comparison:
56
+
57
+ - `if: ${{ fromJSON(matrix.enabled) }}` ✅ — `fromJSON("false")` → boolean `false` (falsy), skips correctly
58
+ - `if: ${{ fromJSON(matrix.enabled) == false }}` ✅ — correct boolean comparison
59
+ - `if: ${{ matrix.enabled }}` ❌ — string `"false"` is truthy, job always runs
60
+ - `if: ${{ matrix.enabled == false }}` ❌ — string vs boolean comparison, always evaluates to false
61
+
62
+ The same `fromJSON()` pattern applies to steps inside the matrix job:
63
+ ```yaml
64
+ steps:
65
+ - name: Deploy step
66
+ if: ${{ fromJSON(matrix.deploy) }}
67
+ run: ./deploy.sh
68
+ ```
69
+ fix_code:
70
+ - language: yaml
71
+ label: 'Correct: fromJSON() converts string to native boolean'
72
+ code: |
73
+ jobs:
74
+ build:
75
+ strategy:
76
+ matrix:
77
+ include:
78
+ - os: ubuntu-latest
79
+ enabled: true
80
+ deploy: false
81
+ - os: windows-latest
82
+ enabled: false
83
+ deploy: false
84
+ runs-on: ${{ matrix.os }}
85
+ # ✅ Correct: fromJSON() parses "false" → boolean false (falsy)
86
+ if: ${{ fromJSON(matrix.enabled) }}
87
+ steps:
88
+ - uses: actions/checkout@v4
89
+
90
+ - name: Deploy (conditional step)
91
+ # ✅ Correct: fromJSON() for step-level boolean matrix property
92
+ if: ${{ fromJSON(matrix.deploy) }}
93
+ run: echo "Deploying on ${{ matrix.os }}"
94
+
95
+ - language: yaml
96
+ label: 'Wrong: string "false" is truthy — job always runs'
97
+ code: |
98
+ jobs:
99
+ build:
100
+ strategy:
101
+ matrix:
102
+ include:
103
+ - os: ubuntu-latest
104
+ enabled: true
105
+ - os: windows-latest
106
+ enabled: false
107
+ runs-on: ${{ matrix.os }}
108
+ # ❌ Wrong: matrix.enabled is the string "false", which is truthy
109
+ if: ${{ matrix.enabled }}
110
+ steps:
111
+ - run: echo "This always runs even when enabled: false"
112
+
113
+ - language: yaml
114
+ label: 'Wrong: string vs boolean comparison always false'
115
+ code: |
116
+ jobs:
117
+ build:
118
+ runs-on: ubuntu-latest
119
+ strategy:
120
+ matrix:
121
+ include:
122
+ - name: job-a
123
+ skip: false
124
+ - name: job-b
125
+ skip: true
126
+ # ❌ Wrong: "false" == false is always false in Actions expressions
127
+ if: ${{ matrix.skip == false }}
128
+ steps:
129
+ - run: echo "This never runs for any include entry"
130
+ prevention:
131
+ - 'Always wrap boolean matrix property references in fromJSON() — e.g., if: ${{ fromJSON(matrix.enabled) }}'
132
+ - 'Add a test matrix entry with the boolean set to false and verify the job is actually skipped before relying on the condition in production'
133
+ - 'Consider using string sentinel values (e.g., skip: "yes"/"no") and comparing with == to avoid the boolean coercion ambiguity entirely'
134
+ - 'Document in the workflow that all matrix properties are runtime strings — reviewers often assume boolean values remain boolean'
135
+ docs:
136
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-a-matrix-for-your-jobs'
137
+ label: 'Using a matrix for your jobs — GitHub Actions docs'
138
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#fromjson'
139
+ label: 'fromJSON() expression function — GitHub Actions docs'
140
+ - url: 'https://stackoverflow.com/questions/77059002/how-to-use-matrix-variables-to-conditionally-run-jobs-in-a-github-actions-workfl'
141
+ label: 'Stack Overflow Q77059002 — matrix boolean coercion and fromJSON() fix'
@@ -0,0 +1,119 @@
1
+ id: silent-failures-104
2
+ title: '`github.event.inputs.X` Returns Empty String (Not Declared Default) for `on: schedule` and Other Non-Dispatch Triggers — Use `inputs.X` Instead'
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - github-event-inputs
7
+ - inputs-context
8
+ - schedule
9
+ - workflow-dispatch
10
+ - multi-trigger
11
+ - empty-string
12
+ - default-value
13
+ - context
14
+ - boolean-input
15
+ patterns:
16
+ - regex: 'github\.event\.inputs\.[a-zA-Z_][a-zA-Z0-9_-]*'
17
+ flags: 'g'
18
+ - regex: 'on:\s*\n(?:.*\n)*?\s+schedule:'
19
+ flags: 'im'
20
+ error_messages:
21
+ - "Deploy target is empty — expected a non-empty value from github.event.inputs.environment"
22
+ - "Error: Input required and not supplied"
23
+ - "github.event.inputs.dry_run was '' but expected 'false'"
24
+ root_cause: |
25
+ Multi-trigger workflows that combine `on: schedule` (or `on: push`, `on: pull_request`,
26
+ etc.) with `on: workflow_dispatch` commonly read input values via
27
+ `${{ github.event.inputs.X }}`. This silently produces wrong results for all
28
+ non-dispatch runs because:
29
+
30
+ - **`github.event.inputs.X`** is populated ONLY when the workflow is triggered via
31
+ `workflow_dispatch`. For every other event type — including `schedule`, `push`,
32
+ and `pull_request` — `github.event.inputs` is null or an empty object. Accessing a
33
+ property returns `""` (empty string). The `default:` declared in the `inputs:` block
34
+ is NEVER applied through this context.
35
+
36
+ - **`inputs.X`** correctly returns the declared `default:` value for non-dispatch
37
+ triggers, and the actual provided value for dispatch-triggered runs.
38
+
39
+ **Silent failure patterns:**
40
+
41
+ 1. **Boolean gate inverted on schedule** — `if: github.event.inputs.dry_run == 'false'`
42
+ evaluates to `false` on schedule runs (because `"" != "false"`), so deployment steps
43
+ that should run on the nightly schedule are silently skipped.
44
+
45
+ 2. **Non-empty guard always fails** — `if: github.event.inputs.target != ''` is always
46
+ `false` on schedule even when the intended behavior is to run with the default target.
47
+
48
+ 3. **String comparison breaks** — `${{ github.event.inputs.environment == 'staging' }}`
49
+ is `false` on schedule because the empty string does not match any environment name.
50
+
51
+ Unlike `inputs.X`, `github.event.inputs.X` has no concept of a fallback default — it
52
+ returns `""` for every non-dispatch event.
53
+
54
+ **Distinct from sf-077** (which covers `workflow_call` — where `github.event.inputs`
55
+ is null because reusable workflows use the `inputs` context, not `github.event.inputs`).
56
+ **Distinct from sf-072** (which covers the `inputs.X` context being empty on non-dispatch,
57
+ not the `github.event.inputs.X` context).
58
+ fix: |
59
+ Replace all `github.event.inputs.X` references with `inputs.X` in any workflow that
60
+ uses multiple triggers. The `inputs` context is populated for `workflow_dispatch` and
61
+ `workflow_call` events, and returns the declared `default:` value for all other triggers.
62
+ fix_code:
63
+ - language: yaml
64
+ label: "Broken — github.event.inputs.X ignores defaults on schedule runs"
65
+ code: |
66
+ on:
67
+ schedule:
68
+ - cron: '0 2 * * *'
69
+ workflow_dispatch:
70
+ inputs:
71
+ dry_run:
72
+ type: boolean
73
+ default: false
74
+ environment:
75
+ type: string
76
+ default: production
77
+
78
+ jobs:
79
+ deploy:
80
+ steps:
81
+ # ❌ On schedule: dry_run="" (not "false"), environment="" (not "production")
82
+ - run: echo "Env=${{ github.event.inputs.environment }}"
83
+ # ❌ This if-condition is always false on schedule — step silently skipped!
84
+ - if: github.event.inputs.dry_run == 'false'
85
+ run: ./deploy.sh --env ${{ github.event.inputs.environment }}
86
+ - language: yaml
87
+ label: "Fixed — use inputs.X which applies declared defaults on all non-dispatch runs"
88
+ code: |
89
+ on:
90
+ schedule:
91
+ - cron: '0 2 * * *'
92
+ workflow_dispatch:
93
+ inputs:
94
+ dry_run:
95
+ type: boolean
96
+ default: false
97
+ environment:
98
+ type: string
99
+ default: production
100
+
101
+ jobs:
102
+ deploy:
103
+ steps:
104
+ # ✓ On schedule: dry_run="false", environment="production" (defaults applied)
105
+ - run: echo "Env=${{ inputs.environment }}"
106
+ # ✓ Correctly runs on schedule (dry_run defaults to "false")
107
+ - if: inputs.dry_run == 'false'
108
+ run: ./deploy.sh --env ${{ inputs.environment }}
109
+ prevention:
110
+ - "Always use `inputs.X` (not `github.event.inputs.X`) in any workflow with more than one trigger — `inputs.X` applies declared defaults for non-dispatch runs"
111
+ - "Audit all `github.event.inputs.*` references in workflows that also include `on: schedule`, `on: push`, or `on: pull_request` triggers"
112
+ - "Add a debug step in the workflow to log `inputs.*` values on each run — catching empty-input regressions before they cause silent deploy failures"
113
+ docs:
114
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#inputs-context"
115
+ label: "GitHub Actions inputs context — recommended approach for reading workflow inputs"
116
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#context-availability"
117
+ label: "Context availability — github.event.inputs is only set for workflow_dispatch triggers"
118
+ - url: "https://github.com/orgs/community/discussions"
119
+ label: "GitHub Community Discussions — multi-trigger workflows with workflow_dispatch inputs"
@@ -0,0 +1,100 @@
1
+ id: triggers-069
2
+ title: 'push tags: filter silently stops all branch-push triggers — branches: filter must be added separately to also run on branch commits'
3
+ category: triggers
4
+ severity: silent-failure
5
+ tags:
6
+ - push
7
+ - tags-filter
8
+ - branches
9
+ - silent-failure
10
+ - trigger-missing
11
+ - filter-whitelist
12
+ patterns:
13
+ - regex: 'on:\s*\n\s+push:\s*\n\s+tags:'
14
+ flags: 'i'
15
+ error_messages: []
16
+ root_cause: |
17
+ When a workflow specifies on: push: with only a tags: filter and no branches: filter,
18
+ ALL branch push events are silently excluded. The workflow fires exclusively on tag
19
+ pushes matching the tags: pattern.
20
+
21
+ This contradicts the common mental model that tags: adds an extra "also trigger on
22
+ these tags" condition on top of the default "trigger on all branch pushes" behavior.
23
+ In reality, any filter key under on: push: (branches:, tags:, branches-ignore:,
24
+ tags-ignore:, paths:) replaces the implicit "trigger on everything" default with a
25
+ whitelist. Specifying tags: without branches: means only tags match.
26
+
27
+ A workflow with:
28
+ on:
29
+ push:
30
+ tags:
31
+ - 'v*'
32
+
33
+ will never fire when code is pushed to main, feature branches, or any other branch.
34
+ If the team also needs CI on commits to main or pull request merges, a separate
35
+ branches: filter is required in the same on: push: block.
36
+
37
+ This is the inverse of the documented "branches: filter does not block tag pushes"
38
+ behavior (triggers-055). Both stem from the same whitelist-per-filter-key design.
39
+ fix: |
40
+ Add an explicit branches: filter alongside tags: to restore branch-push triggering:
41
+
42
+ on:
43
+ push:
44
+ branches:
45
+ - main
46
+ tags:
47
+ - 'v*'
48
+
49
+ If only tag-based triggering is intended (e.g., a release workflow), the original
50
+ configuration is correct. Document the intent clearly so future maintainers do not
51
+ accidentally add branches: thinking CI was "broken".
52
+
53
+ To trigger on ALL branch pushes plus specific tags, omit branches: from on: push:
54
+ and add a separate on: push: tags: entry — but note that on: push: without filters
55
+ and on: push: tags: cannot coexist in the same block; split into two trigger blocks
56
+ using pull_request for branch coverage and push: tags: for tags.
57
+ fix_code:
58
+ - language: yaml
59
+ label: 'Bug: tags: filter silently excludes all branch-push events'
60
+ code: |
61
+ on:
62
+ push:
63
+ tags:
64
+ - 'v*'
65
+ # Workflow NEVER runs on branch pushes — only fires when a v* tag is pushed
66
+ - language: yaml
67
+ label: 'Fix: add branches: filter to also run on branch commits'
68
+ code: |
69
+ on:
70
+ push:
71
+ branches:
72
+ - main
73
+ - 'release/**'
74
+ tags:
75
+ - 'v*'
76
+
77
+ jobs:
78
+ ci:
79
+ runs-on: ubuntu-latest
80
+ steps:
81
+ - uses: actions/checkout@v4
82
+ - run: make test
83
+ - language: yaml
84
+ label: 'Intentional tag-only release workflow (correct if branch CI is handled in a separate workflow)'
85
+ code: |
86
+ # release.yml — intentionally runs only on release tags
87
+ on:
88
+ push:
89
+ tags:
90
+ - 'v[0-9]+.[0-9]+.[0-9]+'
91
+ prevention:
92
+ - 'Remember that any filter key under on: push: creates a whitelist — adding tags: without branches: stops all branch-push triggers silently'
93
+ - 'Keep release tag workflows in a dedicated file (release.yml) separate from branch-CI workflows (ci.yml) to avoid accidentally merging their trigger logic'
94
+ - 'After adding a tags: filter to an existing workflow, push a test commit to a branch and verify the workflow appears in the Actions tab'
95
+ - 'Use nektos/act or GitHub Actions VS Code extension to preview which events match a workflow trigger before merging'
96
+ docs:
97
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#push'
98
+ label: 'GitHub Docs: push event filters'
99
+ - url: 'https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onpushbranchestagsbranches-ignoretags-ignore'
100
+ label: 'GitHub Docs: Workflow syntax — on.push filter keys'
@@ -0,0 +1,110 @@
1
+ id: yaml-syntax-070
2
+ title: "continue-on-error expression using inputs.* silently resolves null at composite action call site \u2014 \"Unexpected value ''\""
3
+ category: yaml-syntax
4
+ severity: error
5
+ tags:
6
+ - continue-on-error
7
+ - composite-actions
8
+ - inputs-context
9
+ - expression-scoping
10
+ - template-validation
11
+ patterns:
12
+ - regex: 'Unexpected value\s+''''$'
13
+ flags: 'i'
14
+ - regex: 'error.*determining.*continue on error'
15
+ flags: 'i'
16
+ - regex: 'The template is not valid.*Unexpected value\s+'''
17
+ flags: 'i'
18
+ error_messages:
19
+ - "Error: .github/workflows/test.yml (Line: N, Col: N): Unexpected value ''"
20
+ - 'Error: The step failed and an error occurred when attempting to determine whether to continue on error.'
21
+ - "Error: The template is not valid. .github/workflows/test.yml (Line: N, Col: N): Unexpected value ''"
22
+ root_cause: |
23
+ When continue-on-error on a uses: step that calls a composite action contains an
24
+ expression referencing inputs.*, the expression is evaluated inside the composite
25
+ action context — not the calling workflow context. Inside the composite action,
26
+ inputs refers to the composite action''s own declared inputs, not the caller''s
27
+ workflow_dispatch inputs or other workflow-level inputs.
28
+
29
+ If the composite action does not declare that input, inputs.my_flag resolves to
30
+ null, which becomes an empty string in the expression context. The runner then
31
+ tries to parse the empty string as a boolean and emits:
32
+
33
+ Error: Unexpected value ''
34
+
35
+ followed by:
36
+
37
+ Error: The step failed and an error occurred when attempting to determine
38
+ whether to continue on error.
39
+
40
+ The workflow then halts at that step regardless of what the caller intended.
41
+
42
+ This context-crossing is unique to uses: steps (composite actions). For run:
43
+ steps in the same job, inputs.my_flag evaluates in the workflow context and
44
+ works correctly with continue-on-error.
45
+ fix: |
46
+ Replace inputs.* with github.event.inputs.* in the continue-on-error expression
47
+ when calling composite actions from a workflow_dispatch event. github.event.inputs
48
+ is evaluated in the workflow/runner context before the composite action is entered,
49
+ so it resolves correctly.
50
+
51
+ Alternatively, capture the input as a job-level env or step output and reference
52
+ the env var or step output in the continue-on-error expression.
53
+ fix_code:
54
+ - language: yaml
55
+ label: 'Bug: inputs.continue resolves to null inside composite action context'
56
+ code: |
57
+ on:
58
+ workflow_dispatch:
59
+ inputs:
60
+ continue:
61
+ type: boolean
62
+ default: false
63
+
64
+ jobs:
65
+ test:
66
+ runs-on: ubuntu-latest
67
+ steps:
68
+ - uses: my-org/fail-action@main
69
+ # inputs.continue resolves to null here — "Unexpected value ''"
70
+ continue-on-error: ${{ github.event_name == 'workflow_dispatch' && inputs.continue }}
71
+ - language: yaml
72
+ label: 'Fix: use github.event.inputs.* instead of inputs.* in continue-on-error on uses: steps'
73
+ code: |
74
+ on:
75
+ workflow_dispatch:
76
+ inputs:
77
+ continue:
78
+ type: boolean
79
+ default: false
80
+
81
+ jobs:
82
+ test:
83
+ runs-on: ubuntu-latest
84
+ steps:
85
+ - uses: my-org/fail-action@main
86
+ # github.event.inputs evaluates in workflow context, not composite context
87
+ continue-on-error: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.continue == 'true' }}
88
+ - language: yaml
89
+ label: 'Alternative fix: pre-evaluate the flag into an env var'
90
+ code: |
91
+ jobs:
92
+ test:
93
+ runs-on: ubuntu-latest
94
+ env:
95
+ SHOULD_CONTINUE: ${{ inputs.continue }}
96
+ steps:
97
+ - uses: my-org/fail-action@main
98
+ continue-on-error: ${{ env.SHOULD_CONTINUE == 'true' }}
99
+ prevention:
100
+ - 'Never reference inputs.* in continue-on-error on a uses: step — use github.event.inputs.* for workflow_dispatch inputs or env.* for pre-evaluated values'
101
+ - 'Remember that expression contexts differ between run: steps (workflow context) and uses: composite action steps (composite context)'
102
+ - 'Test continue-on-error with expressions locally using nektos/act before merging to catch context-crossing issues'
103
+ - 'If a composite action needs conditional behavior, declare it as an explicit composite input and pass the value from the caller'
104
+ docs:
105
+ - url: 'https://github.com/actions/runner/issues/2418'
106
+ label: 'GitHub runner#2418: continue-on-error with inputs variables in composite actions (bug, 12 reactions)'
107
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#context-availability'
108
+ label: 'GitHub Docs: Expression context availability'
109
+ - url: 'https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-composite-action'
110
+ label: 'GitHub Docs: Creating a composite action'
@@ -0,0 +1,137 @@
1
+ id: yaml-syntax-068
2
+ title: 'Composite Action run: Steps Use Caller Workspace as Working Directory — github.action_path Required for Bundled Scripts'
3
+ category: yaml-syntax
4
+ severity: error
5
+ tags:
6
+ - composite-action
7
+ - github-action-path
8
+ - working-directory
9
+ - relative-path
10
+ - script
11
+ - file-not-found
12
+ patterns:
13
+ - regex: 'run:\s*\./[^\s]+'
14
+ flags: 'i'
15
+ - regex: 'No such file or directory.*\./'
16
+ flags: 'i'
17
+ - regex: 'bash:.*\./.*: No such file or directory'
18
+ flags: 'i'
19
+ error_messages:
20
+ - "bash: ./scripts/build.sh: No such file or directory"
21
+ - "/bin/bash: ./entrypoint.sh: No such file or directory"
22
+ - "Error: Process completed with exit code 127."
23
+ - "bash: line 1: ./setup.sh: No such file or directory"
24
+ root_cause: |
25
+ When a composite action (whether local `uses: ./` or published `uses: org/action@v1`) runs a
26
+ `run:` step, the **working directory is the caller's workspace** (`github.workspace`), not the
27
+ action's own directory. Any relative path like `run: ./scripts/build.sh` resolves against the
28
+ caller repository root — not the composite action's repository.
29
+
30
+ This affects both published composite actions (referenced as `uses: org/my-action@v1`) and
31
+ local composite actions stored inside the same repository. Developers commonly write:
32
+
33
+ ```yaml
34
+ # In action.yml of org/my-action
35
+ runs:
36
+ using: composite
37
+ steps:
38
+ - name: Run bundled script
39
+ run: ./scripts/build.sh # ❌ Resolves against CALLER workspace, not action directory
40
+ shell: bash
41
+ ```
42
+
43
+ When a caller uses `uses: org/my-action@v1`, `./scripts/build.sh` resolves to the caller's
44
+ `$GITHUB_WORKSPACE/scripts/build.sh`, which does not exist — producing "No such file or
45
+ directory" or exit code 127.
46
+
47
+ **Contrast with JavaScript/Docker actions:** In those action types, the action executes in its
48
+ own context. Composite actions are different — their steps are injected into the caller's
49
+ job environment and run with the caller's working directory.
50
+
51
+ The correct way to reference files bundled inside a composite action is to use
52
+ `${{ github.action_path }}`, which always points to the directory containing the action's
53
+ `action.yml` file, regardless of where the action is called from.
54
+ fix: |
55
+ Replace relative paths in composite action `run:` steps with `${{ github.action_path }}/`:
56
+
57
+ ```yaml
58
+ # In action.yml
59
+ runs:
60
+ using: composite
61
+ steps:
62
+ - name: Run bundled script
63
+ run: ${{ github.action_path }}/scripts/build.sh # ✅ Always resolves to action directory
64
+ shell: bash
65
+ ```
66
+
67
+ If the script must be made executable, add a `chmod` step using `github.action_path`:
68
+
69
+ ```yaml
70
+ steps:
71
+ - name: Make script executable
72
+ run: chmod +x ${{ github.action_path }}/scripts/build.sh
73
+ shell: bash
74
+
75
+ - name: Run bundled script
76
+ run: ${{ github.action_path }}/scripts/build.sh
77
+ shell: bash
78
+ ```
79
+
80
+ `github.action_path` is always set to the directory of the currently executing action's
81
+ `action.yml`, even for deeply nested composite actions calling other composite actions.
82
+ fix_code:
83
+ - language: yaml
84
+ label: 'Correct: github.action_path for bundled scripts'
85
+ code: |
86
+ # In action.yml of your composite action (org/my-action)
87
+ name: 'My Action'
88
+ description: 'Does something useful'
89
+ runs:
90
+ using: composite
91
+ steps:
92
+ - name: Make script executable
93
+ run: chmod +x ${{ github.action_path }}/scripts/build.sh
94
+ shell: bash
95
+
96
+ - name: Run bundled build script
97
+ # ✅ github.action_path always points to the action's directory
98
+ run: ${{ github.action_path }}/scripts/build.sh
99
+ shell: bash
100
+
101
+ - name: Run inline Python from action directory
102
+ run: python ${{ github.action_path }}/tools/generate.py
103
+ shell: bash
104
+
105
+ - language: yaml
106
+ label: 'Wrong: relative path resolves against caller workspace'
107
+ code: |
108
+ # In action.yml of your composite action (org/my-action)
109
+ name: 'My Action'
110
+ runs:
111
+ using: composite
112
+ steps:
113
+ - name: Run bundled script
114
+ # ❌ Wrong: ./scripts/build.sh resolves against the CALLER's $GITHUB_WORKSPACE
115
+ # Fails with: bash: ./scripts/build.sh: No such file or directory
116
+ run: ./scripts/build.sh
117
+ shell: bash
118
+
119
+ # ❌ Also wrong: explicitly using github.workspace points to caller repo root
120
+ - name: Run script using workspace
121
+ run: ${{ github.workspace }}/scripts/build.sh
122
+ shell: bash
123
+
124
+ prevention:
125
+ - 'Never use relative paths (./path) or github.workspace in composite action run: steps to reference the action''s own bundled files — always use github.action_path'
126
+ - 'Test composite actions by calling them from a separate test repository, not just the same repository where they are defined — relative paths that work locally (same repo) silently break when called externally'
127
+ - 'Add a step to verify the script exists at the expected path as the first step of your composite action during development: run: ls ${{ github.action_path }}/scripts/'
128
+ - 'Composite actions that call other composite actions each get their own github.action_path — do not pass it between actions as an input'
129
+ docs:
130
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#github-context'
131
+ label: 'github.action_path context — GitHub Actions docs'
132
+ - url: 'https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-composite-action'
133
+ label: 'Creating a composite action — GitHub Actions docs'
134
+ - url: 'https://github.com/actions/runner/issues/1348'
135
+ label: 'actions/runner#1348 — Local composite actions always relative to top level repository'
136
+ - url: 'https://stackoverflow.com/questions/77033208/github-action-composite-type-not-working-in-other-repositories-due-to-missing'
137
+ label: 'Stack Overflow — Composite action not working in other repositories due to missing action files'