Skip to content

Commit 1381999

Browse files
authored
fix(ci): preflight generated PR app permissions (#103671)
1 parent bbc845a commit 1381999

7 files changed

Lines changed: 302 additions & 107 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Create generated PR tokens
2+
description: Mint repo-scoped GitHub App tokens for generated branch and pull request publication.
3+
4+
inputs:
5+
contents-client-id:
6+
description: GitHub App client id with contents write access.
7+
required: true
8+
contents-private-key:
9+
description: GitHub App private key with contents write access.
10+
required: true
11+
pull-request-app-id:
12+
description: GitHub App id with pull-request write access.
13+
required: true
14+
pull-request-private-key:
15+
description: GitHub App private key with pull-request write access.
16+
required: true
17+
18+
outputs:
19+
contents-token:
20+
description: Repository-scoped contents-write installation token.
21+
value: ${{ steps.contents-token.outputs.token }}
22+
pull-request-token:
23+
description: Repository-scoped pull-request-write installation token.
24+
value: ${{ steps.pull-request-token.outputs.token }}
25+
26+
runs:
27+
using: composite
28+
steps:
29+
- name: Create generated branch app token
30+
id: contents-token
31+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
32+
with:
33+
client-id: ${{ inputs.contents-client-id }}
34+
private-key: ${{ inputs.contents-private-key }}
35+
owner: ${{ github.repository_owner }}
36+
repositories: ${{ github.event.repository.name }}
37+
permission-contents: write
38+
39+
- name: Create generated PR app token
40+
id: pull-request-token
41+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
42+
with:
43+
app-id: ${{ inputs.pull-request-app-id }}
44+
private-key: ${{ inputs.pull-request-private-key }}
45+
owner: ${{ github.repository_owner }}
46+
repositories: ${{ github.event.repository.name }}
47+
permission-pull-requests: write

.github/actions/publish-generated-pr/action.yml

Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ name: Publish generated pull request
22
description: Commit generated files to an automation branch and open or update its pull request.
33

44
inputs:
5-
primary-private-key:
6-
description: Primary OpenClaw GitHub App private key.
5+
contents-client-id:
6+
description: GitHub App client id with contents write access.
77
required: true
8-
fallback-private-key:
9-
description: Fallback OpenClaw GitHub App private key.
8+
contents-private-key:
9+
description: GitHub App private key with contents write access.
1010
required: true
1111
pull-request-app-id:
1212
description: GitHub App id with pull-request write access.
@@ -39,39 +39,20 @@ inputs:
3939
runs:
4040
using: composite
4141
steps:
42-
- name: Create generated branch app token
43-
id: contents-token
44-
continue-on-error: true
45-
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
42+
- name: Create generated PR tokens
43+
id: tokens
44+
uses: ./.github/actions/create-generated-pr-tokens
4645
with:
47-
app-id: "2729701"
48-
private-key: ${{ inputs.primary-private-key }}
49-
permission-contents: write
50-
51-
- name: Create generated branch fallback app token
52-
id: contents-token-fallback
53-
if: steps.contents-token.outcome == 'failure'
54-
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
55-
with:
56-
app-id: "2971289"
57-
private-key: ${{ inputs.fallback-private-key }}
58-
permission-contents: write
59-
60-
- name: Create generated PR app token
61-
id: pull-request-token
62-
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
63-
with:
64-
app-id: ${{ inputs.pull-request-app-id }}
65-
private-key: ${{ inputs.pull-request-private-key }}
66-
owner: ${{ github.repository_owner }}
67-
repositories: ${{ github.event.repository.name }}
68-
permission-pull-requests: write
46+
contents-client-id: ${{ inputs.contents-client-id }}
47+
contents-private-key: ${{ inputs.contents-private-key }}
48+
pull-request-app-id: ${{ inputs.pull-request-app-id }}
49+
pull-request-private-key: ${{ inputs.pull-request-private-key }}
6950

7051
- name: Publish generated pull request
7152
shell: bash
7253
env:
73-
CONTENTS_TOKEN: ${{ steps.contents-token.outputs.token || steps.contents-token-fallback.outputs.token }}
74-
GH_TOKEN: ${{ steps.pull-request-token.outputs.token }}
54+
CONTENTS_TOKEN: ${{ steps.tokens.outputs.contents-token }}
55+
GH_TOKEN: ${{ steps.tokens.outputs.pull-request-token }}
7556
BASE_BRANCH: ${{ inputs.base-branch }}
7657
HEAD_BRANCH: ${{ inputs.head-branch }}
7758
COMMIT_MESSAGE: ${{ inputs.commit-message }}
@@ -88,7 +69,7 @@ runs:
8869
export GIT_TERMINAL_PROMPT=0
8970
9071
if [[ -z "${CONTENTS_TOKEN:-}" ]]; then
91-
echo "Generated branch publication requires an OpenClaw contents-write App token." >&2
72+
echo "Generated branch publication requires a contents-write App token." >&2
9273
exit 1
9374
fi
9475
if [[ -z "${GH_TOKEN:-}" ]]; then

.github/workflows/control-ui-locale-refresh.yml

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ on:
1111
- ui/src/i18n/lib/types.ts
1212
- ui/src/i18n/lib/registry.ts
1313
- scripts/control-ui-i18n.ts
14+
- .github/actions/create-generated-pr-tokens/action.yml
1415
- .github/actions/publish-generated-pr/action.yml
1516
- .github/workflows/control-ui-locale-refresh.yml
1617
release:
@@ -19,14 +20,23 @@ on:
1920
schedule:
2021
- cron: "23 4 * * *"
2122
workflow_dispatch:
23+
inputs:
24+
token_preflight_only:
25+
description: Verify generated PR App permissions without running locale generation.
26+
required: false
27+
default: false
28+
type: boolean
2229

2330
permissions:
2431
contents: read
2532

2633
concurrency:
27-
group: control-ui-locale-refresh
28-
# Finish the active full refresh; GitHub retains only the newest pending run for this group.
29-
# Publisher overlap checks defer stale generated paths to that queued reconciliation run.
34+
group: >-
35+
${{ github.event_name == 'workflow_dispatch' && inputs.token_preflight_only &&
36+
format('control-ui-locale-token-preflight-{0}', github.ref) ||
37+
'control-ui-locale-refresh' }}
38+
# Full refreshes stay serialized; cheap App-permission probes get a ref-scoped group.
39+
# Publisher overlap checks defer stale generated paths to the queued full reconciliation run.
3040
cancel-in-progress: false
3141

3242
jobs:
@@ -38,27 +48,57 @@ jobs:
3848
outputs:
3949
sha: ${{ steps.base.outputs.sha }}
4050
steps:
41-
- name: Resolve default branch head
51+
- name: Resolve source commit
4252
id: base
4353
env:
4454
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
4555
GH_TOKEN: ${{ github.token }}
4656
REPOSITORY: ${{ github.repository }}
57+
TOKEN_PREFLIGHT_ONLY: ${{ inputs.token_preflight_only || false }}
58+
WORKFLOW_SHA: ${{ github.workflow_sha }}
4759
run: |
4860
set -euo pipefail
49-
sha="$(
50-
timeout --signal=TERM --kill-after=10s 60s \
51-
gh api --method GET "repos/${REPOSITORY}/commits/${DEFAULT_BRANCH}" --jq .sha
52-
)"
61+
if [[ "${TOKEN_PREFLIGHT_ONLY}" == "true" ]]; then
62+
sha="${WORKFLOW_SHA}"
63+
else
64+
sha="$(
65+
timeout --signal=TERM --kill-after=10s 60s \
66+
gh api --method GET "repos/${REPOSITORY}/commits/${DEFAULT_BRANCH}" --jq .sha
67+
)"
68+
fi
5369
if [[ ! "${sha}" =~ ^[0-9a-f]{40}$ ]]; then
54-
echo "Unable to resolve ${DEFAULT_BRANCH} to an exact commit." >&2
70+
echo "Unable to resolve the locale refresh source to an exact commit." >&2
5571
exit 1
5672
fi
5773
echo "sha=${sha}" >> "${GITHUB_OUTPUT}"
5874
59-
refresh:
75+
publisher-preflight:
76+
name: Verify generated PR App permissions
6077
needs: resolve-base
6178
if: needs.resolve-base.result == 'success'
79+
runs-on: ubuntu-latest
80+
steps:
81+
- name: Checkout
82+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
83+
with:
84+
ref: ${{ needs.resolve-base.outputs.sha }}
85+
persist-credentials: false
86+
submodules: false
87+
88+
- name: Create generated PR tokens
89+
uses: ./.github/actions/create-generated-pr-tokens
90+
with:
91+
contents-client-id: Iv23liOECG0slfuhz093
92+
contents-private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
93+
pull-request-app-id: ${{ secrets.MANTIS_GITHUB_APP_ID }}
94+
pull-request-private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
95+
96+
refresh:
97+
needs: [resolve-base, publisher-preflight]
98+
if: >-
99+
needs.resolve-base.result == 'success' &&
100+
needs.publisher-preflight.result == 'success' &&
101+
!(github.event_name == 'workflow_dispatch' && inputs.token_preflight_only)
62102
strategy:
63103
fail-fast: false
64104
max-parallel: 4
@@ -199,8 +239,12 @@ jobs:
199239

200240
finalize:
201241
name: Commit control UI locale refresh
202-
needs: [resolve-base, refresh]
203-
if: needs.resolve-base.result == 'success' && needs.refresh.result == 'success'
242+
needs: [resolve-base, publisher-preflight, refresh]
243+
if: >-
244+
needs.resolve-base.result == 'success' &&
245+
needs.publisher-preflight.result == 'success' &&
246+
needs.refresh.result == 'success' &&
247+
!(github.event_name == 'workflow_dispatch' && inputs.token_preflight_only)
204248
runs-on: ubuntu-latest
205249
steps:
206250
- name: Checkout
@@ -238,8 +282,8 @@ jobs:
238282
- name: Open or update generated locale PR
239283
uses: ./.github/actions/publish-generated-pr
240284
with:
241-
primary-private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
242-
fallback-private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
285+
contents-client-id: Iv23liOECG0slfuhz093
286+
contents-private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
243287
pull-request-app-id: ${{ secrets.MANTIS_GITHUB_APP_ID }}
244288
pull-request-private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
245289
base-branch: ${{ github.event.repository.default_branch }}
@@ -254,6 +298,7 @@ jobs:
254298
ui/src/i18n/lib/types.ts
255299
ui/src/i18n/lib/registry.ts
256300
scripts/control-ui-i18n.ts
301+
.github/actions/create-generated-pr-tokens/action.yml
257302
.github/actions/publish-generated-pr/action.yml
258303
.github/workflows/control-ui-locale-refresh.yml
259304
pr-body: |

.github/workflows/native-app-locale-refresh.yml

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ on:
1515
- scripts/control-ui-i18n.ts
1616
- scripts/native-app-i18n.ts
1717
- ui/src/i18n/.i18n/glossary.*.json
18+
- .github/actions/create-generated-pr-tokens/action.yml
1819
- .github/actions/publish-generated-pr/action.yml
1920
- .github/workflows/native-app-locale-refresh.yml
2021
workflow_dispatch:
@@ -55,9 +56,32 @@ jobs:
5556
fi
5657
echo "sha=${sha}" >> "${GITHUB_OUTPUT}"
5758
58-
refresh:
59+
publisher-preflight:
60+
name: Verify generated PR App permissions
5961
needs: resolve-base
6062
if: needs.resolve-base.result == 'success'
63+
runs-on: ubuntu-latest
64+
steps:
65+
- name: Checkout
66+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
67+
with:
68+
ref: ${{ needs.resolve-base.outputs.sha }}
69+
persist-credentials: false
70+
submodules: false
71+
72+
- name: Create generated PR tokens
73+
uses: ./.github/actions/create-generated-pr-tokens
74+
with:
75+
contents-client-id: Iv23liOECG0slfuhz093
76+
contents-private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
77+
pull-request-app-id: ${{ secrets.MANTIS_GITHUB_APP_ID }}
78+
pull-request-private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
79+
80+
refresh:
81+
needs: [resolve-base, publisher-preflight]
82+
if: >-
83+
needs.resolve-base.result == 'success' &&
84+
needs.publisher-preflight.result == 'success'
6185
strategy:
6286
fail-fast: false
6387
max-parallel: 2
@@ -199,8 +223,11 @@ jobs:
199223

200224
finalize:
201225
name: Commit native locale refresh
202-
needs: [resolve-base, refresh]
203-
if: needs.resolve-base.result == 'success' && needs.refresh.result == 'success'
226+
needs: [resolve-base, publisher-preflight, refresh]
227+
if: >-
228+
needs.resolve-base.result == 'success' &&
229+
needs.publisher-preflight.result == 'success' &&
230+
needs.refresh.result == 'success'
204231
runs-on: ubuntu-latest
205232
steps:
206233
- name: Checkout
@@ -243,8 +270,8 @@ jobs:
243270
- name: Open or update generated locale PR
244271
uses: ./.github/actions/publish-generated-pr
245272
with:
246-
primary-private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
247-
fallback-private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
273+
contents-client-id: Iv23liOECG0slfuhz093
274+
contents-private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
248275
pull-request-app-id: ${{ secrets.MANTIS_GITHUB_APP_ID }}
249276
pull-request-private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
250277
base-branch: ${{ github.event.repository.default_branch }}
@@ -263,6 +290,7 @@ jobs:
263290
scripts/control-ui-i18n.ts
264291
scripts/native-app-i18n.ts
265292
ui/src/i18n/.i18n/glossary.*.json
293+
.github/actions/create-generated-pr-tokens/action.yml
266294
.github/actions/publish-generated-pr/action.yml
267295
.github/workflows/native-app-locale-refresh.yml
268296
pr-body: |

scripts/test-projects.test-support.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,10 @@ const TOOLING_SOURCE_TEST_TARGETS = new Map([
632632
],
633633
[".crabbox.yaml", ["test/scripts/package-acceptance-workflow.test.ts"]],
634634
[".github/actions/detect-docs-changes/action.yml", ["test/scripts/ci-workflow-guards.test.ts"]],
635+
[
636+
".github/actions/create-generated-pr-tokens/action.yml",
637+
["test/scripts/ci-workflow-guards.test.ts"],
638+
],
635639
[".github/actions/publish-generated-pr/action.yml", ["test/scripts/ci-workflow-guards.test.ts"]],
636640
[
637641
".github/actions/docker-e2e-plan/action.yml",

0 commit comments

Comments
 (0)