Skip to content

Commit ea46338

Browse files
Document valid caller concurrency policies
GitHub Actions requires static queue values and rejects queue: max with cancellation. Separate production and pull-request caller jobs preserve both required behaviors. Co-authored-by: Copilot <223556698+Copilot@users.noreply.github.com>
1 parent b6dde9d commit ea46338

8 files changed

Lines changed: 171 additions & 120 deletions

File tree

.github/plugin/psmodule/skills/psmodule-v8-upgrade/SKILL.md

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,30 @@ on:
165165
- labeled
166166
- unlabeled
167167

168-
concurrency:
169-
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
170-
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
171-
172168
permissions: {}
173169

174170
jobs:
175-
Process-PSModule:
171+
Process-PSModule-Production:
172+
if: ${{ github.event_name != 'pull_request' }}
173+
concurrency:
174+
group: ${{ github.workflow }}-${{ github.ref }}
175+
queue: max
176+
permissions:
177+
contents: read
178+
pages: write
179+
id-token: write
180+
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8
181+
secrets:
182+
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
183+
GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }}
184+
GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }}
185+
186+
Process-PSModule-PullRequest:
187+
if: ${{ github.event_name == 'pull_request' }}
188+
concurrency:
189+
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
190+
queue: single
191+
cancel-in-progress: true
176192
permissions:
177193
contents: read
178194
pages: write
@@ -184,11 +200,10 @@ jobs:
184200
GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }}
185201
```
186202
187-
The only permitted variation is an optional `TestData` entry under
188-
`jobs.Process-PSModule.secrets`. Do not add `with:` inputs, extra jobs,
189-
conditions, schedule changes, `run-name`, permission changes, trigger changes,
190-
concurrency changes, debug options, or version overrides. Repository-owned
191-
automation belongs in separate workflow files.
203+
The only permitted variation is an optional identical `TestData` entry under both caller jobs' `secrets` mappings.
204+
Do not add `with:` inputs, extra jobs, conditions, schedule changes, `run-name`, permission changes, trigger changes,
205+
concurrency changes, debug options, or version overrides. Repository-owned automation belongs in separate workflow
206+
files.
192207

193208
### TestData preservation
194209

docs/content/get-started/repository-setup.md

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,33 +52,51 @@ on:
5252
- labeled
5353
- unlabeled
5454

55-
concurrency:
56-
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
57-
queue: ${{ github.event_name == 'pull_request' && 'single' || 'max' }}
58-
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
59-
60-
permissions:
61-
contents: read
62-
pages: write
63-
id-token: write
55+
permissions: {}
6456

6557
jobs:
66-
Process-PSModule:
58+
Process-PSModule-Production:
59+
if: ${{ github.event_name != 'pull_request' }}
60+
concurrency:
61+
group: ${{ github.workflow }}-${{ github.ref }}
62+
queue: max
63+
permissions:
64+
contents: read
65+
pages: write
66+
id-token: write
67+
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8
68+
secrets:
69+
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
70+
GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }}
71+
GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }}
72+
73+
Process-PSModule-PullRequest:
74+
if: ${{ github.event_name == 'pull_request' }}
75+
concurrency:
76+
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
77+
queue: single
78+
cancel-in-progress: true
79+
permissions:
80+
contents: read
81+
pages: write
82+
id-token: write
6783
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8
6884
secrets:
6985
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
7086
GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }}
7187
GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }}
7288
```
7389
74-
Every permission in that block is required. GitHub App installation tokens perform repository writes. A push to `main` publishes a stable release after the full pipeline passes;
75-
the pull-request trigger handles CI, prereleases, and prerelease cleanup. See
90+
Every permission on the calling jobs is required. GitHub App installation tokens perform repository writes. A push to
91+
`main` publishes a stable release after the full pipeline passes; the pull-request trigger handles CI, prereleases,
92+
and prerelease cleanup. See
7693
[Workflow inputs](../reference/workflow-inputs.md) for what each permission is used for, and
7794
[Calling the workflow](../guides/calling-the-workflow.md) for passing test secrets and variables.
7895

79-
The caller-level concurrency block retains production and manual work while replacing obsolete work for the same pull
80-
request. The fallback expression uses the pull-request number for every pull-request action, including `closed`; other
81-
events use their Git ref. Keep its group distinct from the reusable workflow's prefixed group.
96+
The production job retains pushes, dispatches, and scheduled work in the maximum native queue. The pull-request job
97+
cancels obsolete activity and uses the pull-request number for every action, including `closed`. GitHub requires a
98+
literal queue value and does not permit cancellation with `queue: max`, so the two jobs use separate compatible
99+
policies. Keep both groups distinct from the reusable workflow's prefixed group.
82100

83101
## 4. Add the settings file
84102

docs/content/guides/calling-the-workflow.md

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,34 @@ on:
3535
- labeled
3636
- unlabeled
3737

38-
concurrency:
39-
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
40-
queue: ${{ github.event_name == 'pull_request' && 'single' || 'max' }}
41-
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
42-
43-
permissions:
44-
contents: read
45-
pages: write
46-
id-token: write
38+
permissions: {}
4739

4840
jobs:
49-
Process-PSModule:
41+
Process-PSModule-Production:
42+
if: ${{ github.event_name != 'pull_request' }}
43+
concurrency:
44+
group: ${{ github.workflow }}-${{ github.ref }}
45+
queue: max
46+
permissions:
47+
contents: read
48+
pages: write
49+
id-token: write
50+
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8
51+
secrets:
52+
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
53+
GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }}
54+
GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }}
55+
56+
Process-PSModule-PullRequest:
57+
if: ${{ github.event_name == 'pull_request' }}
58+
concurrency:
59+
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
60+
queue: single
61+
cancel-in-progress: true
62+
permissions:
63+
contents: read
64+
pages: write
65+
id-token: write
5066
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8
5167
secrets:
5268
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
@@ -60,10 +76,11 @@ Stable releases are evaluated from a push to the default branch. A merged pull r
6076
release notes; a direct default-branch push or a manual dispatch uses the default `Patch` bump and commit-based notes.
6177
Keep the `pull_request` trigger for CI, prereleases, and prerelease cleanup.
6278

63-
The concurrency key keeps each pull request distinct from the default branch, so a close event interrupts only its own
64-
pull-request activity and does not block the resulting stable release. Pull-request events replace obsolete activity;
65-
all other events, including a manual default-branch release, retain the maximum native queue. The reusable workflow
66-
uses a distinct prefixed concurrency group. Do not give the caller the reusable workflow's group name.
79+
The production job serializes default-branch pushes, manual releases, and scheduled work in the maximum native queue.
80+
The pull-request job uses a separate group for each pull request, so a close event interrupts only its own activity and
81+
does not block the resulting stable release. Its `single` queue and cancellation replace obsolete activity. GitHub
82+
requires a literal queue value and rejects cancellation with `queue: max`, so these policies must remain separate.
83+
The reusable workflow uses a distinct prefixed concurrency group. Do not give either caller job that group name.
6784

6885
## Passing test data
6986

@@ -85,21 +102,14 @@ The reusable workflow accepts test data through `TestData` and no longer declare
85102
- `TEST_USER_USER_FG_PAT`
86103
- `TEST_USER_PAT`
87104

88-
If a caller passed any of these secrets directly, place them in the `secrets` map inside `TestData`.
89-
The environment variable names used by the tests can stay the same; only the workflow-call interface
90-
changes:
105+
If a caller passed any of these secrets directly, place them in the `secrets` map inside `TestData`. Add the same
106+
`TestData` mapping to the `secrets` block of both caller jobs. The environment variable names used by the tests can
107+
stay the same; only the workflow-call interface changes:
91108

92109
```yaml
93-
jobs:
94-
Process-PSModule:
95-
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8
96-
secrets:
97-
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
98-
GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }}
99-
GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }}
100-
TestData: >-
101-
{ "secrets": { "TEST_USER_PAT": "${{ secrets.TEST_USER_PAT }}",
102-
"TEST_APP_ORG_CLIENT_ID": "${{ secrets.TEST_APP_ORG_CLIENT_ID }}" } }
110+
TestData: >-
111+
{ "secrets": { "TEST_USER_PAT": "${{ secrets.TEST_USER_PAT }}",
112+
"TEST_APP_ORG_CLIENT_ID": "${{ secrets.TEST_APP_ORG_CLIENT_ID }}" } }
103113
```
104114

105115
### Passing test phase data (secrets and variables)
@@ -112,25 +122,17 @@ workflow. It is one JSON object with two maps, so everything the tests need is v
112122
{ "secrets": { "NAME": "value" }, "variables": { "NAME": "value" } }
113123
```
114124

115-
Values under `secrets` are masked in the logs; values under `variables` are not. Build it in the
116-
calling workflow and pass it through the `secrets:` block (so the whole blob is masked). Reference each
117-
secret directly as `"${{ secrets.<name> }}"` and each variable as `${{ toJSON(vars.<name>) }}`. A
118-
folded `>-` scalar keeps the source readable while producing a single-line value, as long as the JSON
119-
content lines stay at the same indentation level:
125+
Values under `secrets` are masked in the logs; values under `variables` are not. Build it in the calling workflow and
126+
pass it through the `secrets:` blocks of both caller jobs so the whole blob is masked. Reference each secret directly
127+
as `"${{ secrets.<name> }}"` and each variable as `${{ toJSON(vars.<name>) }}`. A folded `>-` scalar keeps the source
128+
readable while producing a single-line value, as long as the JSON content lines stay at the same indentation level:
120129

121130
```yaml
122-
jobs:
123-
Process-PSModule:
124-
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8
125-
secrets:
126-
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
127-
GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }}
128-
GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }}
129-
TestData: >-
130-
{ "secrets": { "CONFLUENCE_API_TOKEN": "${{ secrets.CONFLUENCE_API_TOKEN }}" },
131-
"variables": { "CONFLUENCE_SITE": ${{ toJSON(vars.CONFLUENCE_SITE) }},
132-
"CONFLUENCE_USERNAME": ${{ toJSON(vars.CONFLUENCE_USERNAME) }},
133-
"CONFLUENCE_SPACE_KEY": ${{ toJSON(vars.CONFLUENCE_SPACE_KEY) }} } }
131+
TestData: >-
132+
{ "secrets": { "CONFLUENCE_API_TOKEN": "${{ secrets.CONFLUENCE_API_TOKEN }}" },
133+
"variables": { "CONFLUENCE_SITE": ${{ toJSON(vars.CONFLUENCE_SITE) }},
134+
"CONFLUENCE_USERNAME": ${{ toJSON(vars.CONFLUENCE_USERNAME) }},
135+
"CONFLUENCE_SPACE_KEY": ${{ toJSON(vars.CONFLUENCE_SPACE_KEY) }} } }
134136
```
135137

136138
Each entry becomes an environment variable in the test jobs, so the module's Pester tests read the

docs/content/guides/github-app-authentication.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,18 @@ The reusable workflow declares two required secrets at its `workflow_call` bound
1818
| `GitHubAppPrivateKey` | The GitHub App private key passed to the token action. |
1919

2020
The names are the reusable workflow contract, not a requirement for the caller's repository or organization secret
21-
names. Map the caller's secrets explicitly:
21+
names. Map the caller's secrets explicitly in both caller jobs:
2222

2323
```yaml
2424
jobs:
25-
Process-PSModule:
25+
Process-PSModule-Production:
26+
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8
27+
secrets:
28+
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
29+
GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }}
30+
GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }}
31+
32+
Process-PSModule-PullRequest:
2633
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8
2734
secrets:
2835
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}

docs/content/reference/repository-standard.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,19 @@ The module repository owns a caller workflow; the framework owns the reusable wo
117117
| Caller workflow | The module repository | `.github/workflows/Process-PSModule.yml` |
118118
| Reusable workflow | [`PSModule/Process-PSModule`](https://github.com/PSModule/Process-PSModule) | `.github/workflows/workflow.yml` |
119119

120-
The caller workflow declares the triggers, concurrency, and permissions for the module repository, and delegates the work:
120+
The caller workflow declares the triggers, concurrency, and permissions for the module repository. Its production and
121+
pull-request jobs use the same reusable workflow and explicit secret mapping:
121122

122123
```yaml
123124
jobs:
124-
Process-PSModule:
125+
Process-PSModule-Production:
126+
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@<commit-sha> # <version tag>
127+
secrets:
128+
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
129+
GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }}
130+
GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }}
131+
132+
Process-PSModule-PullRequest:
125133
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@<commit-sha> # <version tag>
126134
secrets:
127135
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}

docs/content/specification/design.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@ documented in [Workflow inputs](../reference/workflow-inputs.md).
1919

2020
### Trigger admission
2121

22-
The [workflow trigger design](workflow-triggers/design.md) owns caller-level admission before processing: a retained
23-
production queue and replaceable pull-request activity. The caller's workflow-level group covers the complete reusable
24-
workflow call through its final enabled stage; the reusable workflow identifies closure and performs optional prerelease
25-
cleanup.
22+
The [workflow trigger design](workflow-triggers/design.md) owns caller admission before processing: a retained
23+
production queue and replaceable pull-request activity. The caller jobs cover each complete reusable-workflow call
24+
through its final enabled stage; the reusable workflow identifies closure and performs optional prerelease cleanup.
2625

2726
### Composed reusable workflows
2827

0 commit comments

Comments
 (0)