Skip to content
This repository was archived by the owner on Oct 26, 2026. It is now read-only.

Commit cf24190

Browse files
ci(test-shard-timings-refresh): push and open the refresh pull request as the fleet App so it runs CI (#13362)
<!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4Uu2XCfGL2rwSd3BXynetsBjb)_ Clause-②: no Metadata-protocol: none Part of #13349 (step 2). Step 1, the one-time landing of #13028, is on that pull request. ## 维护者速读(草稿) - **改了什么:** 每周刷新分片耗时的 workflow,推分支和开 PR 不再用 Actions 自带的 token,改为在 runner 上用 fleet App(`objectstack-fleet[bot]`)临时铸一个 token:只对 objectui 一个仓、只给 contents / pull-requests / workflows 写权限,job 结束即吊销。job 自己的 token 降为只读。文档 `ci-cd-pipeline.md` 同步改写那一节,并在密钥表里登记这对组织级凭据。 - **为什么改:** Actions token 发出的事件 GitHub 不触发 CI,所以刷新 PR 每周都拿不到 CI,只能等人推一下。#13028 从 10-19 等到 10-23,这期间分片不均 1.3–1.8 倍,拖慢每一张 PR。 - **风险与代价(含回滚):** objectui 的一个 workflow 步骤开始读组织密钥 `OS_FLEET_PRIVATE_KEY`,这是唯一新增的信任面;PR 跑(含 fork)不铸 token、不读密钥。凭据对本仓不可见时,周一那次 run 会红并写明原因,不会悄悄退回旧 token。回滚就是 revert 这一个提交。 - **席位意见:** - **你要做的:** 确认组织密钥 `OS_FLEET_PRIVATE_KEY` 与变量 `OS_FLEET_APP_ID` 对 objectui 可见,并确认 #5397 对 App token 的否决只管 release PR;两点都同意就批准本 PR。 ## What changed `.github/workflows/test-shard-timings-refresh.yml`: - New step `Check the fleet App credential is visible to this repository`: reads `vars.OS_FLEET_APP_ID != ''` and `secrets.OS_FLEET_PRIVATE_KEY != ''` as booleans (nothing printed) and goes red with a message naming the missing one. It also writes the token's one repository name. - New step `Mint the fleet App token, narrowed to this repository`: `actions/create-github-app-token@v3` (the same action and credential pair objectstack's `fleet-write.yml` mints with), `repositories` = this repository only, `permission-contents`, `permission-pull-requests` and `permission-workflows` write. The last is there because the weekly force-push moves the branch from last week's base to today's `main`, across whatever workflow files landed in between, which GitHub refuses for an App token without it; the workflow writes no workflow file. - `Open or update the refresh pull request` now uses that token (`gh auth setup-git` for the push), commits as `objectstack-fleet[bot]`, and its pull request body says the PR runs its own CI instead of asking for a human push. - The job's `GITHUB_TOKEN` drops from `contents: write` + `pull-requests: write` to `contents: read` (plus the existing `actions: read`); checkout no longer persists a credential. - Both new steps share the existing `github.event_name != 'pull_request'` guard, so a pull-request run (including a fork's) never reads the App secret. - The header's "The cost it carries, stated" section is rewritten as "Whose identity opens the pull request, and why", carrying the #5397 / #13349 reasoning and the two prohibitions (no personal access token, no fallback to `GITHUB_TOKEN`). `content/docs/guide/ci-cd-pipeline.md`: the workflow's section, its inventory row, and the secrets table. No changeset: `node scripts/check-changeset-presence.mjs` says no released package's source or contract moved. ## Governing text - The ruling recorded on #13349 (Sales, 2026-10-23, option A), and the two maintainer confirmations it asks for there: the organization credential's visibility to this repository, and #5397's rejection read as scoped to the release pull request. - objectui#5397 (no personal access token in automation): kept. ## Verification At head `c543446`, exit codes captured before any pipe: - `pnpm exec vitest run scripts/__tests__` :: exit 0 (190 files passed, 2 skipped; 5727 tests passed) - `node scripts/check-action-ref-convention.mjs` :: exit 0 (14 distinct refs, 0 off-convention) - `pnpm run check:control-bytes` / `check:doc-fences` / `docs:check-links` / `check:new-line-citations` :: exit 0 each - `node scripts/check-changeset-presence.mjs` :: exit 0 (no changeset owed) - YAML parses. Not observable before merge, stated plainly: the mint, the push and the pull request open only on `schedule` / `workflow_dispatch` on `main`. The first proof is a `workflow_dispatch` of this workflow after merge (or the Monday 06:47 UTC run): the refresh PR should show check runs within minutes with no human push. The `workflows: write` need on a force-push across workflow-file changes is GitHub's documented App-token behaviour, not measured here. ## Acceptance notes - #13028's run on its synced head (`17b3796`, a full-suite PR run) put the 16 test legs at 6.7 to 11.9 minutes of wall clock, about 1.8x. The card's ≤1.2x reading belongs to the first full battery on `main` after #13028 lands; the card session reads it and reports on #13349. --- _Generated by [Claude Code](https://claude.ai/code/session_01Cr2pnminvpXPZAG6sKgzgN)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Cr2pnminvpXPZAG6sKgzgN --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3688e5c commit cf24190

2 files changed

Lines changed: 99 additions & 25 deletions

File tree

‎.github/workflows/test-shard-timings-refresh.yml‎

Lines changed: 83 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,40 @@
3636
# edit, not this workflow's.
3737
# ⛔ It never opens a pull request for a byte-identical file.
3838
#
39-
# ## The cost it carries, stated
39+
# ## Whose identity opens the pull request, and why (objectui#13349)
4040
#
41-
# The pull request is opened with this run's `GITHUB_TOKEN`, and GitHub starts
42-
# no workflow run for an event that token raised -- the release pull request's
43-
# limitation, which objectui#5397's ruling chose to keep rather than add a
44-
# personal access token. So the refresh pull request arrives with no CI; the
45-
# seat or person who lands it pushes to its branch once (a merge of `main` is
46-
# enough), and that push runs the full suite on the new split. The branch is
47-
# this workflow's own and is overwritten by each refresh, the same posture
48-
# `changeset-release.yml` takes with its version branch.
41+
# GitHub starts no workflow run for an event raised by this run's
42+
# `GITHUB_TOKEN`. Until objectui#13349 the refresh pushed and opened with that
43+
# token, so every refresh pull request arrived with no CI and sat until a seat
44+
# or person pushed to its branch: #13028 waited from 2026-10-19 to 2026-10-23
45+
# while the shards ran 1.3-1.8x apart on a dataset missing 764 files.
46+
#
47+
# So the two acts that leave the runner -- the push and the pull request -- are
48+
# done as the fleet App (`objectstack-fleet[bot]`), whose events DO start
49+
# workflows: `actions/create-github-app-token` mints a token on this runner
50+
# from the organization variable `OS_FLEET_APP_ID` and the organization secret
51+
# `OS_FLEET_PRIVATE_KEY`, the same pair and action objectstack's
52+
# `fleet-write.yml` mints with. The token is narrowed to this one repository
53+
# and to the three grants below, exists only in the steps that use it, and is
54+
# revoked by the action's post step. The job's own `GITHUB_TOKEN` keeps only
55+
# the reads the selection and the downloads need.
56+
#
57+
# ⛔ Never a personal access token: objectui#5397's ruling keeps one out of
58+
# this repository's automation, and the fleet App is an installation, not
59+
# a person. The App-token route here is objectui#13349's ruling (Sales,
60+
# 2026-10-23), which reads #5397's rejection as scoped to the release
61+
# pull request, where an in-workflow validation was the alternative; here
62+
# the pull request's own full-suite run IS the validation.
63+
# ⛔ No fallback to `GITHUB_TOKEN`. A missing credential is a red run that
64+
# names it, never a quiet return to a pull request nobody's CI reads.
65+
#
66+
# The branch, `ci/test-shard-timings-refresh`, is this workflow's own and is
67+
# overwritten by each refresh, the same posture `changeset-release.yml` takes
68+
# with its version branch. A force-push moves it from last week's base to
69+
# today's `main`, across whatever workflow files landed in between, and GitHub
70+
# refuses an App token that moves a ref across a workflow-file change unless it
71+
# carries `workflows: write` -- the reason for that third grant; this workflow
72+
# writes no workflow file.
4973
#
5074
# ## Which runs it reads
5175
#
@@ -71,7 +95,12 @@
7195
#
7296
# A pull request that edits this workflow or the scripts it runs executes it
7397
# all -- the selection, the downloads, the regeneration -- and stops before
74-
# the two acts that leave the runner: the push and the pull request.
98+
# the two acts that leave the runner: the push and the pull request. It mints
99+
# no fleet token either, so a pull request's run never reads the App secret.
100+
#
101+
# The same three steps run only from `main`: a manual dispatch on any other
102+
# branch regenerates and prints, and pushes nothing, so the refresh branch is
103+
# only ever built from `main`'s tree.
75104

76105
name: Test Shard Timings Refresh
77106

@@ -103,14 +132,19 @@ jobs:
103132
name: Regenerate the per-file test timings
104133
runs-on: ubuntu-latest
105134
timeout-minutes: 15
135+
# The push and the pull request go out on the fleet App token minted
136+
# below, so this run's own token only reads.
106137
permissions:
107-
contents: write # push this workflow's own branch
108-
pull-requests: write # open the refresh pull request
138+
contents: read # the checkout
109139
actions: read # list runs, read their jobs, download their timing artifacts
110140

111141
steps:
142+
# No persisted credential: the one push goes out on the fleet token
143+
# (`gh auth setup-git` below), never on this run's `GITHUB_TOKEN`.
112144
- name: Checkout code
113145
uses: actions/checkout@v7
146+
with:
147+
persist-credentials: false
114148

115149
# The selector and the generator are node builtins only: no pnpm, no
116150
# install.
@@ -158,18 +192,50 @@ jobs:
158192
git diff --stat -- scripts/test-shard-timings.json
159193
cat "$RUNNER_TEMP/summary.md" >> "$GITHUB_STEP_SUMMARY"
160194
195+
# Read as a boolean, never printed: a red run that names the missing
196+
# credential beats `create-github-app-token`'s own error, which does not.
197+
- name: Check the fleet App credential is visible to this repository
198+
id: credential
199+
if: steps.select.outputs.status == '0' && github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
200+
env:
201+
HAS_APP_ID: ${{ vars.OS_FLEET_APP_ID != '' }}
202+
HAS_PRIVATE_KEY: ${{ secrets.OS_FLEET_PRIVATE_KEY != '' }}
203+
run: |
204+
if [ "$HAS_APP_ID" != 'true' ] || [ "$HAS_PRIVATE_KEY" != 'true' ]; then
205+
echo "::error title=Test shard timings::The fleet App credential is not visible to this repository (OS_FLEET_APP_ID set: $HAS_APP_ID; OS_FLEET_PRIVATE_KEY set: $HAS_PRIVATE_KEY). Both are organization-level; grant this repository access to them. Nothing was pushed (objectui#13349)."
206+
exit 1
207+
fi
208+
# The token's one repository, by name: the action takes names
209+
# without the owner, and an empty list would widen it to every
210+
# repository the installation covers.
211+
echo "repository=${GITHUB_REPOSITORY#*/}" >> "$GITHUB_OUTPUT"
212+
213+
- name: Mint the fleet App token, narrowed to this repository
214+
id: fleet
215+
if: steps.select.outputs.status == '0' && github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
216+
uses: actions/create-github-app-token@v3
217+
with:
218+
app-id: ${{ vars.OS_FLEET_APP_ID }}
219+
private-key: ${{ secrets.OS_FLEET_PRIVATE_KEY }}
220+
owner: ${{ github.repository_owner }}
221+
repositories: ${{ steps.credential.outputs.repository }}
222+
permission-contents: write # push this workflow's own branch
223+
permission-pull-requests: write # open or update the refresh pull request
224+
permission-workflows: write # the force-push crosses workflow files main changed (header)
225+
161226
- name: Open or update the refresh pull request
162-
if: steps.select.outputs.status == '0' && github.event_name != 'pull_request'
227+
if: steps.select.outputs.status == '0' && github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
163228
env:
164-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
229+
GH_TOKEN: ${{ steps.fleet.outputs.token }}
165230
BRANCH: ci/test-shard-timings-refresh
166231
run: |
167232
if git diff --quiet -- scripts/test-shard-timings.json; then
168233
echo "The regenerated dataset is byte-identical to the committed one: no pull request."
169234
exit 0
170235
fi
171-
git config user.name 'github-actions[bot]'
172-
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
236+
gh auth setup-git
237+
git config user.name 'objectstack-fleet[bot]'
238+
git config user.email '332303061+objectstack-fleet[bot]@users.noreply.github.com'
173239
git switch -c "$BRANCH"
174240
git add scripts/test-shard-timings.json
175241
git commit -m 'chore(test): refresh the per-file test timings (objectui#12208)'
@@ -179,7 +245,7 @@ jobs:
179245
echo
180246
cat "$RUNNER_TEMP/summary.md"
181247
echo
182-
echo '**This pull request has no CI of its own yet.** It was opened with the Actions token, and GitHub starts no workflow for an event that token raised. Push to its branch once (a merge of `main` is enough): the dataset is in the root vitest config import closure, so that push runs the full suite on the new split.'
248+
echo 'Pushed and opened as the fleet App (objectui#13349), so this pull request runs its own CI: the dataset is in the root vitest config import closure, so that run is the full suite on the new split.'
183249
} > "$RUNNER_TEMP/body.md"
184250
open=$(gh pr list --repo "$GITHUB_REPOSITORY" --head "$BRANCH" --state open --json number --jq '.[0].number // empty')
185251
if [ -n "$open" ]; then

‎content/docs/guide/ci-cd-pipeline.md‎

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ one has its own section below.
5050
| `merge-queue-head-patrol.yml` | Merge queue head patrol | Every 15 minutes (cron `7,22,37,52 * * * *`); manual | No — it gates no branch and blocks no queue, but it **goes red on a finding**: a merge-queue head with no `merge_group` build is a live repo-wide block |
5151
| `required-check-set-patrol.yml` | Required check set patrol | Daily (cron `23 5 * * *`); manual | No — it gates no branch and blocks no queue, but it **goes red on a finding**: a merge queue whose required set has lost `Type Check` validates nothing that a type error would fail |
5252
| `hook-selftests.yml` | Hook Self-Tests | PR / push touching `.claude/hooks/**` or the workflow | **Yes** |
53-
| `test-shard-timings-refresh.yml` | Regenerate the per-file test timings | Weekly cron `47 6 * * 1`; manual; PR touching the workflow or the two scripts it loads | No — it is path-filtered, so it does not report on most PRs; **blocking when it does run**. Its scheduled run opens a pull request and gates nothing |
53+
| `test-shard-timings-refresh.yml` | Regenerate the per-file test timings | Weekly cron `47 6 * * 1`; manual; PR touching the workflow or the two scripts it loads | No — it is path-filtered, so it does not report on most PRs; **blocking when it does run**. Its scheduled run opens a pull request as the fleet App, so that pull request runs CI, and gates nothing |
5454

5555
The path filters explain most "why did nothing run on my PR?" questions:
5656

@@ -2997,15 +2997,22 @@ full suite on the new split before it lands.
29972997
timings, or after a week of red — the selector exits 3, the job prints a warning naming the reason
29982998
and stays green, and nothing is written. A NOT MEASURED that persists is the finding.
29992999

3000-
**The refresh pull request arrives without CI.** It is opened with the run's `GITHUB_TOKEN`, and
3001-
GitHub starts no workflow for an event that token raised — the same limitation the release pull
3002-
request carries, which [#5397](https://github.com/objectstack-ai/objectui/issues/5397)'s ruling
3003-
kept rather than add a personal access token. Whoever lands it pushes to its branch once (a merge
3004-
of `main` is enough). The branch, `ci/test-shard-timings-refresh`, is this workflow's own and is
3000+
**The refresh pull request runs its own CI.** GitHub starts no workflow for an event raised by a
3001+
run's `GITHUB_TOKEN`, so until [#13349](https://github.com/objectstack-ai/objectui/issues/13349)
3002+
every refresh pull request arrived without CI and waited for someone to push to its branch. The
3003+
push and the pull request now go out as the fleet App (`objectstack-fleet[bot]`):
3004+
`actions/create-github-app-token` mints a token on the runner from the organization variable
3005+
`OS_FLEET_APP_ID` and secret `OS_FLEET_PRIVATE_KEY`, narrowed to this repository and to
3006+
`contents`, `pull-requests` and `workflows` write (the force-push moves the branch across any
3007+
workflow files `main` changed since the last refresh), and revokes it when the job ends. The job's
3008+
own token only reads. ⛔ No personal access token ([#5397](https://github.com/objectstack-ai/objectui/issues/5397)),
3009+
and ⛔ no fallback to `GITHUB_TOKEN`: when the credential is not visible to this repository the run
3010+
goes red and says so. The branch, `ci/test-shard-timings-refresh`, is this workflow's own and is
30053011
overwritten by each refresh, as `changeset-release.yml` does with its version branch.
30063012

3007-
**A pull-request run writes nothing.** It runs the selection, the downloads and the regeneration,
3008-
and skips the push and the pull request.
3013+
**A pull-request run writes nothing**, and neither does a manual dispatch on any branch but `main`.
3014+
It runs the selection, the downloads and the regeneration, and skips the mint, the push and the
3015+
pull request.
30093016

30103017
### Merge Queue Head Patrol (`merge-queue-head-patrol.yml`)
30113018

@@ -3456,6 +3463,7 @@ on:
34563463
|-------------------|---------|---------|
34573464
| `GITHUB_TOKEN` | All workflows | GitHub API access (automatic) |
34583465
| `NPM_TOKEN` | `changeset-release.yml` | npm package publishing |
3466+
| `OS_FLEET_APP_ID` (variable), `OS_FLEET_PRIVATE_KEY` (secret) | `test-shard-timings-refresh.yml` | Mint the fleet App token the refresh pushes its branch and opens its pull request with, so that pull request runs CI. Both are **organization**-level. If either is not visible to this repository, the run fails red. |
34593467
| `CROSS_REPO_ISSUE_TOKEN` | `cross-repo-issue-closer.yml` | Closing issues in sibling repositories. `GITHUB_TOKEN` cannot do this — it is scoped to the repository running the workflow. When absent the workflow reports instead of closing. |
34603468
| `VERCEL_TOKEN` | `docs-deploy.yml` | Vercel API token for the Object Stack team, used by `vercel pull` and `vercel deploy`. It is stored as an **organization** secret. If it is missing, the run fails red. |
34613469
| `TURBO_TOKEN` | Build workflows | Turbo remote cache authentication |

0 commit comments

Comments
 (0)