You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ci(workflows): move nested shell into bun scripts for the shell-complexity gate - #469
$ bun check-shell-complexity.ts .
.github/workflows/auto-fix.yml:111: if inside if
.github/workflows/auto-fix.yml:154: function
.github/workflows/auto-fix.yml:158: $(...) tested inside if
.github/workflows/auto-fix.yml:167: case inside while
.github/workflows/auto-fix.yml:203: if inside if
.github/workflows/auto-format.yml:97: $(...) tested inside if
.github/workflows/nightly.yml:53: if inside for
.github/workflows/nightly.yml:78: || inside if
.github/workflows/nightly.yml:79: if inside if
.github/workflows/update-release-pr.yml:98: function
.github/workflows/update-release.yml:153: function
11 finding(s). Shell may nest no construct inside another; move this block to a TypeScript script run by bun, or list the file in .shell-complexity-allow.local with a # reason.
After
$ bun check-shell-complexity.ts .
Shell complexity check passed. Bodies judged: 47 workflow run steps, 2 composite action run steps. 4 managed file(s) skipped; repo-platform owns them.
How
Each flagged block is a bun script subcommand with the same commands, GITHUB_OUTPUT lines, log lines, and exit codes.
lib/workflow-step.ts carries what bash gave the steps:set -e with signal statuses, $(...), the output append.
The npm floor is a release-pipeline.ts npm-floor subcommand comparing through Bun.semver.
The push jobs run the script from a default-branch checkout: bun starts and imports there, so no PR bunfig, .env, hook, or lifecycle script runs under the write token.
The fix-owned allowlist the push job enforces is main's. A same-repo author can still edit the workflow file, as before this PR.
Proof
Tests:bun test test/scripts 408 pass (29 new, 6 negative controls run red on the reintroduced bug), test/docs workflow pins 37 pass.
Gates:tsc, biome, knip, actionlint on the six touched workflows, and the platform gate above, all clean locally.
Technical details
Finding
Script and subcommand
auto-fix.yml:111 (if inside if)
auto-fix-steps.ts rebuild
auto-fix.yml:154, :158, :167, :203
auto-fix-steps.ts push
auto-format.yml:97
auto-format-steps.ts push
nightly.yml:53
nightly-steps.ts probe-schema
nightly.yml:78, :79
nightly-steps.ts probe-types
update-release-pr.yml:98, update-release.yml:153
release-pipeline.ts npm-floor
Push-job boundary:trusted/ is the default branch, bun starts there (its bunfig.toml and .env are main's), and PR_CHECKOUT names branch/, where only git runs.
Applied patches are never executed, and fork PRs skip both pipelines.
Deviation: a git diff --quiet status other than 0 or 1 fails the rebuild instead of reading as pruned=true.
Deviation: npm-floor compares through Bun.semver, not sort -V, and reports a failed upgrade through the script's stderr line with exit 1 instead of the installer's status.
Deviation: probe-schema reads node_modules/<pkg>/package.json directly, since bun's resolver would auto-install a package it does not find, the pin this probe must not test.
Deviation: a failed git rev-parse HEAD in a push step ends the step with git's status and no notice, where the shell's if printed the stale-head notice and exited 0 (fail closed).
Transient: a PR branch that predates this landing lacks the scripts the build job calls from its checkout until it rebases, as with every script a workflow calls.
Deleted: test/scripts/auto-fix-allowlist.test.ts. Its regex parse of the shell is moot, and auto-fix-steps.test.ts pins the list and generator order to the generated-output table.
Rewritten: test/docs/repo-owned-workflows.test.ts pins the trusted/branch shape with in-memory negative controls, and test/docs/npm-publish-workflows.test.ts pins both publishers to the one npm-floor step and a single NPM_FLOOR not below 11.5.1.
Warn-tier lines:.github/actions/setup/action.yml:7 and post-green.yml:29 re-cut into two lines, and the 14-line comment before update-release-pr.yml's publish step cut to 9.
Recorded, not built: requireEnv refuses empty push inputs the shell tolerated.
Recorded, not built: capture buffers whole output where $(...) streamed, bounded at Number.MAX_SAFE_INTEGER.
Recorded, not built: each workflow's HEAD_SHA expression is required present, not pinned to its source.
Recorded, not built: resisting same-repository workflow edits would move the privileged workflow behind a trusted trigger.
Recorded, not built: the boundary test is a lexical tripwire. Computed paths through env aliases, alternate runtime spellings, inherited runtime configuration, and action indirection stay outside it.
Line accounting (git diff --numstat origin/main...HEAD): scripts +505/-2 (5 new files, release-pipeline +47/-2), workflows and the setup composite +61/-190, tests +998/-303 (3 new files, 1 deleted, 2 rewritten in test/docs, release-pipeline test +88/-1).
…exity gate
The platform's check-shell-complexity gate (ci / standard-checks, Vivswan/repo-platform/actions/check-shell-complexity@stable) refuses shell that nests one construct inside another, and any function. It failed main and every PR with 11 findings, all in repo-owned starters: auto-fix.yml (5), auto-format.yml (1), nightly.yml (3), update-release-pr.yml (1), update-release.yml (1).
Each flagged run block is now a TypeScript script run by bun, one subcommand per moved step, with the same commands, GITHUB_OUTPUT lines, log lines, and exit codes:
- auto-fix.yml rebuild and push -> .github/scripts/auto-fix-steps.ts rebuild | push
- auto-format.yml push -> .github/scripts/auto-format-steps.ts push
- nightly.yml schema and types probes -> .github/scripts/nightly-steps.ts probe-schema | probe-types
- the npm floor step of both release workflows -> release-pipeline.ts npm-floor (Bun.semver against 11.5.1)
.github/scripts/lib/workflow-step.ts carries what bash gave those steps (set -e with signal statuses, $(...), GITHUB_OUTPUT) and lib/pr-branch.ts the bot identity and lease push the two push steps share.
The push jobs of auto-fix.yml and auto-format.yml keep their boundary: the script that holds the write token is checked out from the default branch into trusted/, bun starts there (so the bunfig.toml and .env it reads are main's), and the PR branch in branch/ receives only git. The fix-owned allowlist the push job enforces is therefore main's, which a PR cannot widen.
test/scripts/auto-fix-allowlist.test.ts, which regex-parsed the shell, is replaced by test/scripts/auto-fix-steps.test.ts: the fix-owned list and the generator order pinned to the generated-output table, and each step's branches run against fixture repositories. auto-format-steps, nightly-steps (a scripted bun), and npm-floor (a scripted npm) get the same treatment.
Deliberate divergences from the shell: a git diff --quiet status other than 0 or 1 fails the rebuild instead of reading as a prune; npm-floor compares the version through Bun.semver instead of sort -V and reports a failed upgrade with its exit status through the script's uniform stderr line (exit 1); probe-schema reads node_modules/<pkg>/package.json instead of bun -e require, since bun's resolver would auto-install a package it does not find, the pin this probe must not test.
…ry as shaped, and reword its claims
The lead's gate and CI on the first commit asked for three claim and output fixes and two test rewrites, folded into one push.
The npm-floor subcommand prints nothing when the floor holds and, when npm is still below it after the upgrade, the stdout annotation the shell step printed (::error::npm X cannot publish through OIDC; trusted publishing needs npm 11.5.1 or newer.) with exit 1; the npmFloor cases pin stdout, stderr, and status per case.
The auto-fix.yml header, both push jobs' step comments, and the push scripts' comments now say what the trusted/branch split does (bun starts in and imports only default-branch code, no PR bunfig, .env, hook, or lifecycle script runs there, the fix-owned list the push job enforces is main's) and no longer say a PR cannot reach the token: a same-repository author can still edit the workflow file, which predates the split.
test/docs/repo-owned-workflows.test.ts pins the boundary as shaped (the default-branch checkout in trusted, the PR head in branch, bun pinned from trusted with no install, every runtime step in trusted, no local action, no run text naming branch or PR_CHECKOUT, no git push outside the script, one push-script step carrying HEAD_SHA, HEAD_REF, and PR_CHECKOUT) with in-memory negative controls for each. test/docs/npm-publish-workflows.test.ts pins both publishers to the one npm-floor step the script dispatches and to a single NPM_FLOOR not below 11.5.1, in place of the floor= line it read from shell that no longer exists; its bash run of that shell is gone, the npmFloor tests cover those branches.
Three lines the platform's warn tier flagged: .github/actions/setup/action.yml and post-green.yml each had a line over 150 characters, re-cut into two; the comment block before update-release-pr.yml's publish step shrank from 14 lines to 9.
…it is
The docblock on boundaryProblems() claimed every way a push job breaks the boundary. The checks are lexical: the default-branch ref into trusted, the PR head into branch, the working directory, no literal branch or PR_CHECKOUT reference, one push-script step. A path computed through an env alias, another runtime spelling, inherited runtime configuration, or action indirection passes it, and the scan is not widened for them: the comment now says what the test is.
The documented push contract omits PR_CHECKOUT, so invoking the subcommand with only the listed variables exits with PR_CHECKOUT must be set for this step before applying the patch. Add the checkout path to the required environment list.
The documented push contract omits PR_CHECKOUT, so invoking the subcommand with only the listed variables exits with PR_CHECKOUT must be set for this step before applying the patch. Add the checkout path to the required environment list.
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
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.
Before
After
How
GITHUB_OUTPUTlines, log lines, and exit codes.lib/workflow-step.tscarries what bash gave the steps:set -ewith signal statuses,$(...), the output append.release-pipeline.ts npm-floorsubcommand comparing throughBun.semver.Proof
bun test test/scripts408 pass (29 new, 6 negative controls run red on the reintroduced bug),test/docsworkflow pins 37 pass.tsc,biome,knip,actionlinton the six touched workflows, and the platform gate above, all clean locally.Technical details
auto-fix-steps.ts rebuildauto-fix-steps.ts pushauto-format-steps.ts pushnightly-steps.ts probe-schemanightly-steps.ts probe-typesrelease-pipeline.ts npm-floortrusted/is the default branch, bun starts there (itsbunfig.tomland.envare main's), andPR_CHECKOUTnamesbranch/, where only git runs.git diff --quietstatus other than 0 or 1 fails the rebuild instead of reading aspruned=true.npm-floorcompares throughBun.semver, notsort -V, and reports a failed upgrade through the script's stderr line with exit 1 instead of the installer's status.probe-schemareadsnode_modules/<pkg>/package.jsondirectly, since bun's resolver would auto-install a package it does not find, the pin this probe must not test.git rev-parse HEADin a push step ends the step with git's status and no notice, where the shell'sifprinted the stale-head notice and exited 0 (fail closed).test/scripts/auto-fix-allowlist.test.ts. Its regex parse of the shell is moot, andauto-fix-steps.test.tspins the list and generator order to the generated-output table.test/docs/repo-owned-workflows.test.tspins the trusted/branch shape with in-memory negative controls, andtest/docs/npm-publish-workflows.test.tspins both publishers to the onenpm-floorstep and a singleNPM_FLOORnot below 11.5.1..github/actions/setup/action.yml:7andpost-green.yml:29re-cut into two lines, and the 14-line comment beforeupdate-release-pr.yml's publish step cut to 9.requireEnvrefuses empty push inputs the shell tolerated.capturebuffers whole output where$(...)streamed, bounded atNumber.MAX_SAFE_INTEGER.HEAD_SHAexpression is required present, not pinned to its source.git diff --numstat origin/main...HEAD): scripts +505/-2 (5 new files, release-pipeline +47/-2), workflows and the setup composite +61/-190, tests +998/-303 (3 new files, 1 deleted, 2 rewritten intest/docs, release-pipeline test +88/-1)..github/scripts/{auto-fix-steps,auto-format-steps,nightly-steps,release-pipeline}.ts,.github/scripts/lib/{workflow-step,pr-branch}.ts,.github/workflows/{auto-fix,auto-format,nightly,update-release-pr,update-release,post-green}.yml,.github/actions/setup/action.yml,test/docs/{repo-owned-workflows,npm-publish-workflows}.test.ts,test/scripts/{auto-fix-steps,auto-format-steps,nightly-steps,release-pipeline}.test.ts,test/scripts/step-fixture.ts.