Skip to content

Commit 89964c1

Browse files
ci(shard-timings-refresh): push and open the refresh PR as the fleet App so it runs CI (#26017)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D4WbEC4Xfs3EygKcaDSqinr)_ Part of #25936. The card stays open for two readings this PR cannot take before it merges (see "What stays open"). ## 维护者速读(草稿) **改了什么**:每周一自动刷新 Test Core 分片计时数据的 workflow(`shard-timings-refresh.yml`),推分支、开 PR、打标签这三步改用 fleet App(`objectstack-fleet[bot]`)的身份做。令牌在 runner 上现铸,只对本仓、只给 contents 和 pull-requests 两项写权限,用完即吊销。job 自己的 `GITHUB_TOKEN` 降为只读。 **为什么改**:以前用 Actions 自带的 `GITHUB_TOKEN` 开 PR(仓里没有配 `RELEASE_PUSH_TOKEN`),GitHub 对这种 PR 不启动任何 CI。结果每次刷新 PR 都要有人手动推一下才有检查:#24467 等了十天,今天的 #25960 是席位用中继 `pr_update_branch` 才跑起来的。刷新会改变分片切法,恰恰最需要 CI 读一遍。 **风险与代价(含回滚)**:用的是 fleet-write 已在用的那对组织级凭据(`OS_FLEET_APP_ID` / `OS_FLEET_PRIVATE_KEY`),不引入 PAT。凭据不可见时这一步直接报红并点名缺哪一半,不会悄悄退回无 CI 的老路。PR 上跑这个 workflow 时照旧是演练,不铸令牌、不读密钥。回滚就是 revert 这一个文件。ui/ 的逐文件计时数据集这次没接上(见下文),它由 #25712 第 5 项承接。 **席位意见**: **你要做的**:本 PR 是 Tier H(`.github/workflows/**`),需要一位 GOVERNED_APPROVERS 批准;批准后席位落地。 ## What changed `.github/workflows/shard-timings-refresh.yml` only (+78 / −24): - **Two new steps before the write step**, both skipped on `pull_request` and when the dataset is byte-identical: - `Check the fleet App credential is visible to this repository` reads `vars.OS_FLEET_APP_ID` and `secrets.OS_FLEET_PRIVATE_KEY` as booleans (never printed) and reds with a message naming the missing half. - `Mint the fleet App token, narrowed to this repository`: `actions/create-github-app-token@v3`, the same action and credential pair `fleet-write.yml` mints with, and the route ui/'s refresh took in objectstack-ai/objectui#13362. The grants are `contents: write` (push the bot branch) and `pull-requests: write` (open the PR, add `skip-changeset`). It has no `workflows` grant, because the branch is cut from the checked-out `main` plus one commit touching only the dataset. - **Write step**: `GH_TOKEN` is the minted token on a live run and the run's read-only token on a `pull_request` dry run, where `outward` hands it to nothing. A live run with no minted token arrives empty and the existing `GH_TOKEN:?` guard reds it. `outward gh auth setup-git` points git at that token for the push. The commit author is now `objectstack-fleet[bot]`. - **Job permissions** drop from `contents: write` + `pull-requests: write` + `actions: read` to `contents: read` + `actions: read`. The checkout uses `persist-credentials: false` and no longer reads `RELEASE_PUSH_TOKEN`. - **PR body**: the "No checks will start on this PR by themselves" section and its `USED_PAT` switch are gone. It is replaced by one line saying the PR was opened as the fleet App and runs its own CI (the card's step 3). ## The measurement this rests on 1. **The root refresh has the card's no-CI defect.** #25960 (2026-10-26 05:47Z) is authored by `github-actions[bot]`, and its body carries the `USED_PAT != true` section, so `RELEASE_PUSH_TOKEN` is not configured. It got CI only after a relay `pr_update_branch`. #24467 (2026-10-19) sat ten days the same way. 2. **The ui/ per-file dataset has no producer.** `ui/.github/workflows/test-shard-timings-refresh.yml` is nested, so GitHub never runs it. Root `ci.yml`'s `Test UI (N/16)` passes no timings reporter and uploads no `test-file-timings-N` artifact, and its own comment says so: the port belongs to the governance-workflow audit. That audit is #25712 item 5, which names `test-shard-timings-refresh` among the workflows to fold into the objectstack counterpart and widen to `ui/**`. #25712 is in flight under another seat's claim, so this PR does not build that half. The dataset today: `ui/scripts/test-shard-timings.json` was measured 2026-10-19T06:37Z over 6 runs and holds 5,547 keys. Of the 5,805 tracked `*.test.*` files under `ui/` (excluding `e2e/` and `apps/site/`), 290 have no key and are weighed at the median, and 32 keys name files that no longer exist. That count is from `git ls-files` and is an upper bound on what vitest collects. ## Verification Head `cadda147` (`50d4fffd` plus a merge of `origin/main` `a3e5bfa6`, no conflict; this PR's diff against `main` is the one file). - **The write step, driven as written.** Its `run:` block was lifted out of the YAML and run under `bash -e` in a scratch repository: - Dry leg (`DRY_RUN=true`, `pull_request`, stand-in `GH_TOKEN`): exit 0. Every outward act printed `DRY RUN, not executed:`, including `gh auth setup-git`. The local commit was authored `objectstack-fleet[bot]`, email `332303061+objectstack-fleet[bot]@users.noreply.github.com`. - Live leg with an empty `GH_TOKEN` (a run where minting did not happen): exit 1 at the guard, `GH_TOKEN: is not set; gh would run unauthenticated. Nothing was pushed.` - **The credential check, three legs.** Both set: exit 0, and it writes `repository=objectstack`. App id missing: exit 1. Key missing: exit 1. - **Derived gates**: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 42 families. All 42 ran locally with exit 0, including `select-shard-timings-run.mjs --self-test` (it lifts and runs this workflow's selection step), `measure-test-shard-timings.mjs --self-test`, `partition-test-shards.mjs --self-test`, `check-step-collectors`, `check-whole-set-label-write`, `check-workflow-step-name-quoting` and `check:pm-dispatch-gates` (fast tier, 1917 cases). `--ran` reconciled: 42 run, 0 NOT MEASURED. - **This PR's own `pull_request` run of the lane** (the path filter includes the workflow) executes everything up to and including the write step's local commit, with the new `env:`. That is the on-runner proof of the dry leg; read it on the PR's checks. - `check-governed-merges --branch HEAD`: GOVERNED, `.github/workflows/**`, Tier H, 102 changed lines. ## What stays open on the card - **The acceptance reading.** The first live refresh after this lands must show check runs on its PR within 10 minutes, with no human push. That is the next Monday cron (2026-11-02 05:30Z), or sooner through a relay `workflow_dispatch` of `shard-timings-refresh.yml` at `main`. - **The ui/ dataset half.** #25712 item 5 carries it (fold the ui refresh into this lane). #25936 remains open until the seat records how that lands. ## Acceptance notes - `cut-rc.yml` makes the same "PAT if present, Actions token otherwise" choice for the release-candidate PR. It is out of scope for this card and is noted only, not filed. - No changeset: this PR changes a workflow, which publishes nothing (`skip-changeset`). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_014GRKsjPHqpppQDE1LMetJX Co-authored-by: Claude <noreply@anthropic.com>
1 parent db5fca7 commit 89964c1

1 file changed

Lines changed: 78 additions & 24 deletions

File tree

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

Lines changed: 78 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -256,24 +256,19 @@ jobs:
256256
name: Regenerate the shard-timings dataset
257257
runs-on: ubuntu-latest
258258
timeout-minutes: 30
259+
# Reads only. The push, the PR and its label go out on the fleet App token
260+
# the write step mints (see "WHOSE IDENTITY OPENS THE PR" at that step),
261+
# never on this run's own `GITHUB_TOKEN`.
259262
permissions:
260-
# Declaring any `permissions:` block drops every scope not listed, so all
261-
# three are spelled even though only two are writes.
262-
contents: write # push the bot branch
263-
pull-requests: write # open the PR and add its label
263+
contents: read # the checkout, and `git show HEAD:` of the committed dataset
264264
actions: read # list runs, read jobs, download run-summary artifacts
265265
steps:
266+
# No persisted credential: the one push goes out on the fleet token
267+
# (`gh auth setup-git` in the write step), never on `GITHUB_TOKEN`.
266268
- name: Checkout repository
267269
uses: actions/checkout@v7
268270
with:
269-
# A PAT when one exists, the Actions token otherwise — the same choice
270-
# cut-rc.yml makes, for the same reason, and the difference is stated
271-
# in the PR body rather than left to be discovered: a PR opened with
272-
# the Actions `GITHUB_TOKEN` starts NO workflow runs (GitHub's
273-
# recursion guard), so with the fallback credential the refresh PR
274-
# arrives with no CI of its own until someone pushes to it or reopens
275-
# it. With the PAT its checks run immediately.
276-
token: ${{ secrets.RELEASE_PUSH_TOKEN || github.token }}
271+
persist-credentials: false
277272

278273
- name: Setup Node.js
279274
uses: actions/setup-node@v7
@@ -710,7 +705,6 @@ jobs:
710705
RUN_COUNT: ${{ steps.generate.outputs.run_count }}
711706
HEAD_SHA: ${{ steps.generate.outputs.head_sha }}
712707
PARTITIONER_EXIT: ${{ steps.bins.outputs.partitioner_exit }}
713-
USED_PAT: ${{ secrets.RELEASE_PUSH_TOKEN != '' }}
714708
run: |
715709
set -euo pipefail
716710
@@ -907,14 +901,9 @@ jobs:
907901
echo "reports and stops, because both are decisions."
908902
echo
909903
fi
910-
if [ "$USED_PAT" != "true" ]; then
911-
echo "## No checks will start on this PR by themselves"
912-
echo
913-
echo "It was opened with the Actions \`GITHUB_TOKEN\`, and GitHub's recursion guard means a"
914-
echo "PR opened that way triggers no workflow runs. Push any commit to the branch, or close"
915-
echo "and reopen the PR, to start CI."
916-
echo
917-
fi
904+
echo "Pushed and opened as the fleet App (\`objectstack-fleet[bot]\`, #25936), so this PR's"
905+
echo "own events start CI: no push or reopen is needed to get its checks."
906+
echo
918907
# ⛔ No closing keyword is emitted here, in any form. GitHub's
919908
# parser matches `fixes`/`closes`/`resolves` plus a number and
920909
# ignores every negation around them, so even a sentence saying a
@@ -924,6 +913,64 @@ jobs:
924913
} > "$RUNNER_TEMP/pr-body.md"
925914
echo "Composed a $(wc -l < "$RUNNER_TEMP/pr-body.md")-line pull request body."
926915
916+
# WHOSE IDENTITY OPENS THE PR (#25936)
917+
# ------------------------------------
918+
# GitHub starts no workflow run for an event raised by a run's own
919+
# `GITHUB_TOKEN` (its recursion guard). This lane used to push and open
920+
# with that token whenever no PAT was configured, and none was: every
921+
# refresh PR arrived with no CI and sat until a seat pushed to it (#24467,
922+
# 2026-10-19, ten days; #25960, 2026-10-26, started only by a relay
923+
# `pr_update_branch`). A refresh can move pin 3c's slice count, so a PR
924+
# nobody's CI reads is exactly the one that needs reading.
925+
#
926+
# So the acts that leave the runner are done as the fleet App
927+
# (`objectstack-fleet[bot]`), whose events DO start workflows: the two
928+
# steps below mint a token from the organization variable
929+
# `OS_FLEET_APP_ID` and secret `OS_FLEET_PRIVATE_KEY` with
930+
# `actions/create-github-app-token`, the pair and action
931+
# fleet-write.yml mints with, and the one ui/'s refresh already took
932+
# (objectui#13362). The token is narrowed to this repository and to
933+
# contents (push the bot branch) + pull-requests (open the PR, add its
934+
# label), exists only in the write step's `env:`, and is revoked by the
935+
# action's post step. No `workflows` grant: the branch is cut from the
936+
# checked-out `main` and adds one commit that touches only the dataset,
937+
# so the push carries no workflow-file change.
938+
#
939+
# ⛔ No fallback to `GITHUB_TOKEN` and no PAT. A missing credential is a
940+
# red run that names it, never a quiet return to a PR with no CI.
941+
# ⛔ A `pull_request` run mints nothing and never reads the App secret:
942+
# its write step is the dry run, and nothing it does leaves the
943+
# runner.
944+
- name: Check the fleet App credential is visible to this repository
945+
id: credential
946+
if: steps.compare.outputs.changed == 'true' && github.event_name != 'pull_request'
947+
env:
948+
HAS_APP_ID: ${{ vars.OS_FLEET_APP_ID != '' }}
949+
HAS_PRIVATE_KEY: ${{ secrets.OS_FLEET_PRIVATE_KEY != '' }}
950+
run: |
951+
# Read as booleans, never printed: a red that names the missing half
952+
# beats create-github-app-token's own error, which does not.
953+
if [ "$HAS_APP_ID" != 'true' ] || [ "$HAS_PRIVATE_KEY" != 'true' ]; then
954+
echo "::error title=Shard timings refresh::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). Nothing was pushed (#25936)."
955+
exit 1
956+
fi
957+
# The token's one repository, by name: the action takes names
958+
# without the owner, and an empty list would widen it to every
959+
# repository the installation covers.
960+
echo "repository=${GITHUB_REPOSITORY#*/}" >> "$GITHUB_OUTPUT"
961+
962+
- name: Mint the fleet App token, narrowed to this repository
963+
id: fleet
964+
if: steps.compare.outputs.changed == 'true' && github.event_name != 'pull_request'
965+
uses: actions/create-github-app-token@v3
966+
with:
967+
app-id: ${{ vars.OS_FLEET_APP_ID }}
968+
private-key: ${{ secrets.OS_FLEET_PRIVATE_KEY }}
969+
owner: ${{ github.repository_owner }}
970+
repositories: ${{ steps.credential.outputs.repository }}
971+
permission-contents: write # push the bot branch
972+
permission-pull-requests: write # open the PR and add its label
973+
927974
# The WRITE — and, on a `pull_request` run of this lane, its rehearsal.
928975
#
929976
# ONE STEP, ONE `env:`, ONE SCRIPT, ON EVERY EVENT (#18341). The write and
@@ -956,7 +1003,11 @@ jobs:
9561003
# refuses any other spelling, and refuses 'false' on a `pull_request`
9571004
# run, so no edit to this line can make a pull request push.
9581005
DRY_RUN: ${{ github.event_name == 'pull_request' }}
959-
GH_TOKEN: ${{ secrets.RELEASE_PUSH_TOKEN || github.token }}
1006+
# The fleet App token on every live run. A dry run mints none, so it
1007+
# holds the run's own read-only token as a stand-in that `outward`
1008+
# never hands to a network act; a live run with no minted token is
1009+
# empty here and the guard below reds it.
1010+
GH_TOKEN: ${{ github.event_name == 'pull_request' && github.token || steps.fleet.outputs.token }}
9601011
# Every variable the script expands that the runner does not provide.
9611012
# The commit message is the dataset's provenance and names all four:
9621013
# ⛔ never drop one from the message to get a green run.
@@ -1004,8 +1055,11 @@ jobs:
10041055
}
10051056
10061057
BRANCH="claude/shard-timings-refresh-$RUN_ID"
1007-
git config user.name 'github-actions[bot]'
1008-
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
1058+
# The checkout persisted no credential; this points git's helper at
1059+
# GH_TOKEN, the fleet token, for the one push below.
1060+
outward gh auth setup-git
1061+
git config user.name 'objectstack-fleet[bot]'
1062+
git config user.email '332303061+objectstack-fleet[bot]@users.noreply.github.com'
10091063
git switch -c "$BRANCH"
10101064
git add scripts/test-shard-timings.json
10111065
# Separate -m flags rather than one embedded newline: a YAML-indented

0 commit comments

Comments
 (0)