ci(shard-timings): give the write-back step every variable it expands, and rehearse it on pull_request - #19933
Merged
os-support-ai merged 2 commits intoSep 24, 2026
Conversation
…, and rehearse it on pull_request The step that pushes the refresh branch and opens the PR expanded RUN_COUNT and RUNS in its commit message under set -u, but its env: exported only GH_TOKEN, RUN_ID and HEAD_SHA, so the scheduled refresh died at git commit after the dataset had been computed. Export all four generate outputs, as the compose step already does. The pull_request dry run was a separate, mutually exclusive step, so the script that writes never ran before a merge. Fold the two into one step that runs on every event with one env: block; the branch, add and commit run for real on the runner, and every act that leaves it (git push, gh pr create, gh pr view, the label write and read-back) goes through a single outward() switch driven by DRY_RUN. A variable missing from env: now reds the pull_request run that dropped it. The script refuses a DRY_RUN other than true/false, and refuses false on a pull_request run. Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr Co-authored-by: Claude <noreply@anthropic.com>
gh reads GH_TOKEN from the environment, so no shell expansion names it and
a pull_request dry run never starts gh: an env: omission there would still
surface only on the scheduled leg. Name it once, under set -u's sibling
${VAR:?}, so both legs judge it.
Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr
Co-authored-by: Claude <noreply@anthropic.com>
os-support-ai
marked this pull request as ready for review
September 24, 2026 01:18
os-support-ai
enabled auto-merge
September 24, 2026 01:18
os-support-ai
deleted the
claude/issue-18341-shard-timings-writeback-env
branch
September 24, 2026 01:44
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #18341
Clause-②: no
What this changes
One file:
.github/workflows/shard-timings-refresh.yml.env:gaveGH_TOKEN,RUN_ID,HEAD_SHA; the commit message also expandsRUN_COUNTandRUNSunderset -euo pipefail. Both are added from the samesteps.generate.outputsthe compose step already reads. The commit message is unchanged:$RUN_COUNTand$RUNSstay in it (they are the dataset's provenance).pull_requestrehearsal are now ONE step with ONEenv:and ONE script. The old pair was gatedgithub.event_name != 'pull_request'/== 'pull_request', so no PR ever ran the script that writes. Now the step runs on every event (if: steps.compare.outputs.changed == 'true'). On a PR run the branch, thegit addand the commit (whose message expands every provenance variable) run for real on the runner. Every act that leaves the runner goes through one shell function,outward, driven byDRY_RUN: ${{ github.event_name == 'pull_request' }}. That coversgit push,gh pr create,gh pr view, the label POST and its read-back. On a dry runoutwardprints the fully expanded command and returns a stand-in value. The shell expands arguments beforeoutwardis entered, soset -ujudges them on both legs. A key missing fromenv:now reds the PR run that dropped it.DRY_RUNother thantrue/false. It also refusesfalsewhenGITHUB_EVENT_NAMEispull_request, and an absentDRY_RUNis itself an unbound variable.GH_TOKENis read bygh, not by the shell, so a dry run would never notice it missing. The script names it once as${GH_TOKEN:?…}, so both legs judge it too.pull_requestparagraph and the compose step's comment now describe the new shape.⛔ Not touched:
MAX_SHARD_OVER_MEAN,MAX_MEASURED_OVER_PREDICTED,timeout-minutes, the shard matrix,FILE_SHARDED_PACKAGES, or any threshold. The diff has zero lines naming any of them.Measured: both directions (acceptance item 2)
Harness: each leg lifts the step's
run:script out of the YAML (parsed withyaml). It evaluates the step's OWNenv:block into the environment, using the values the failing scheduled run carried (RUN_ID=34808103618,RUN_COUNT=1,HEAD_SHA=a90a9f2679…) and a fake token. It adds only the runner defaults (GITHUB_REPOSITORY,GITHUB_EVENT_NAME,RUNNER_TEMP,GITHUB_STEP_SUMMARY,GITHUB_OUTPUT,HOME,PATH,CI) underenv -i, and runs the script withbash -e, the shell the job log shows for these steps. The scratch repo matches the runner's checkout: a depth-1 clone ofmainwithcore.hooksPath=.githooks, which the runner'spnpm installregisters (job log line:git integration registered (merge.os-regen.name, merge.os-regen.driver, core.hooksPath)). So the repo's realpre-commitandpre-pushhooks run. PATH shims sit in front ofgh(records the call, answers a canned value) andgit push(records the call, forwards only whenoriginis a local bare repo). Nothing left the machine.Base script = blob
fe5a62ef1c(origin/mainfdeeea0cc9). Fixed script = blob2b997d121b(this PR's headd4ba97991e).line 9: RUN_COUNT: unbound variable: byte-for-byte the CI failure of run 34810389734. 0 pushes, 0 gh callspre-pushran (✓ check:commit-card-trailers: 1 commit message(s) … carry no card relation). Branch reached the local origin. 5 recorded calls:git push,gh pr create,gh pr view, label POST, label read-backRUNSRUNS: unbound variable, no commit, 0 pushesDRY RUN, not executed: …. Summary carries the dry-run sectionRUN_COUNTRUN_COUNT: unbound variable: the PR leg now catches the defect classGH_TOKENGH_TOKEN: is not set; gh would run unauthenticated.DRY_RUN=false::error::DRY_RUN is 'false' on a pull_request run …, 0 callsDRY_RUNDRY_RUN: unbound variableDRY_RUN=yes::error::DRY_RUN must be 'true' or 'false', got 'yes'.The exit codes were read from each run directly, never through a pipe. The fix commits came first, and every leg ran against those committed blobs.
Audit of the whole file (acceptance item 3)
Method: every
run:block parsed out of the YAML. A scanner that skips single-quoted text and${{ }}lists each shell expansion ($X,${X}, arithmetic names) and eachprocess.env.Xread by an inlinenode -e. Each name is classified as: stepenv:, assigned in the script,$GITHUB_ENVfrom an earlier step, or a runner default. The file has no workflow-level or job-levelenv:and nodefaults:. Positive control: over the base file the scanner reports exactly two UNRESOLVED names,RUN_COUNTandRUNSin the write step. Over this PR's head it reports zero.set -uenv:GITHUB_ENV(runner)failedis local)RUNNER_TEMP(runner)GITHUB_TOKENRUNNER_TEMP,GITHUB_OUTPUT,GITHUB_STEP_SUMMARY(runner);GITHUB_TOKENread by the child scriptGITHUB_TOKENGITHUB_TOKEN(step),RUNNER_TEMP,GITHUB_REPOSITORY,GITHUB_OUTPUT(runner);RUN_ID,RUN_COUNTetc. are LOCAL hereRUNNER_TEMP,GITHUB_OUTPUT,GITHUB_STEP_SUMMARYRUNNER_TEMP,GITHUB_OUTPUTRUN_ID,RUNS,RUN_COUNT,HEAD_SHA,PARTITIONER_EXIT,USED_PATPARTITIONER_EXITalso defaulted:-0),GITHUB_REPOSITORY,RUNNER_TEMP; node readsRUNNER_TEMP,RUN_IDGH_TOKEN,RUN_ID,HEAD_SHARUN_COUNT,RUNS: UNRESOLVEDDRY_RUN,GH_TOKEN,RUN_ID,RUNS,RUN_COUNT,HEAD_SHAGITHUB_EVENT_NAME,GITHUB_REPOSITORY,RUNNER_TEMP,GITHUB_STEP_SUMMARY(runner)GITHUB_STEP_SUMMARY; its other values are${{ }}expressions, substituted before bashNo second instance of the defect class exists in this file. After this PR no step's execution depends on the event:
DRY_RUNis the file's onlygithub.event_nametest. The remaining legs split on data, not on the event (changedtrue/false, the selector's exit 3), and the selector's own--self-testalready drives its exit-3 leg.Static read of what runs after the commit (never executed on GitHub; ⛔ not asserted to pass)
USED_PAT: false, soGH_TOKENand the checkout credential were the Actionsgithub.token, with jobpermissionscontents: write,pull-requests: write,actions: read.git push origin claude/shard-timings-refresh-RUN_ID: it needscontents: write(declared).GET /repos/…/rules/branches/claude/shard-timings-refresh-1answers[], and the one repository ruleset (main) targets~DEFAULT_BRANCHonly. The repo'spre-pushhook runs on the runner and passed in leg L2 on a depth-1 clone. NOT MEASURED: classic branch-protection patterns (no read path from this seat).gh pr createwith the Actions token: this needs the repository setting that lets GitHub Actions create pull requests. The seat cannot read it (GET /repos/…/actions/permissions/workflowanswers 403 through the agent proxy). Circumstantial evidence: chore: version packages #17076 (chore: version packages, open) was authored bygithub-actions[bot]on 2026-09-09, andrelease.ymlsays changesets/action opens it with the default token. NOT MEASURED for this workflow.issues/N/labelswithpull-requests: writeand noissues:scope: in-repo precedent ispr-automation.yml, whose label-writing jobs declare exactlycontents: readpluspull-requests: write. The label exists (GET /labels/skip-changesetanswers 200), so no create is implied.Owed after merge: acceptance item 4
Not triggered here, by design. After merge, dispatch the lane on
mainand read the PR it opens:(REST equivalent:
POST /repos/objectstack-ai/objectstack/actions/workflows/shard-timings-refresh.yml/dispatcheswith body{"ref":"main"}.)workflow_dispatchevaluatesDRY_RUNtofalse, so that run writes.Changeset
None. The diff is
.github/only and publishes nothing from any package, so it takes route 2 of theCheck Changesetgate, theskip-changesetlabel. An empty-frontmatter changeset is rejected by that gate. This PR does not apply labels; that is the seat's.Gates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsatd4ba97991ederived 40 commands, all run from this worktree. All 40 exited 0 with their own verdict lines.pnpm check:pm-dispatch-gatesran detached, as its header prescribes:✓ dispatch-gates self-test: 1905 cases pass.in 722.1s.--ranreconciliation:40 derived famil(ies) accounted for — 40 run, 0 NOT-MEASURED (a DERIVED zero — all 40 recorded an exit code and none of them is 3). Also run: the five roster families the derivation flags as living under.github/workflows(check-platform-checklist-watchdogplus its self-test,check-ci-filter-parityplus its self-test,ci/scheduled-full-run --self-test,pr-labels --self-test), all exit 0. Noactionlintor other workflow linter exists in the repo's tooling or on this container. NOT MEASURED: the type-check lanes and the six workflow-valued families the derivation lists as CI-only.Acceptance notes
pull_requestrun is not in the merge queue's required set, so its red is advisory. Making it required is the maintainer's call, and this PR does not do it.measuredAtis the run's date, so that is every PR run except one on the same UTC day as a landed refresh with identical inputs.claude/shard-timings-refresh-RUN_IDbranch and be refused as non-fast-forward. This is not observed, only read.维护者速读(草稿)
workflow_dispatch,确认它真开出刷新 PR。Generated by Claude Code