Skip to content

ci(workflows): move nested shell into bun scripts for the shell-complexity gate - #469

Merged
Vivswan merged 3 commits into
mainfrom
ci/shell-complexity
Oct 5, 2026
Merged

Vivswan merged 3 commits into
mainfrom
ci/shell-complexity

Conversation

@Vivswan

@Vivswan Vivswan commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Before

$ 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).
  • Files: .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.

…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.
Copilot AI balanced review requested due to automatic review settings October 5, 2026 09:29
@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

File size check

0 over a hard cap (fails), 10 warning(s).

File Size Tier Cap
.github/scripts/gen-inputs-table.ts:54 11 comment lines warn 10
.github/scripts/release-pipeline.ts:1 34 comment lines (header) warn 25
.github/scripts/release-pipeline.ts:453 14 comment lines warn 10
docs/upgrading/v2-to-v3.md 1292 lines warn 1040
src/github/secret-scan.ts:45 11 comment lines warn 10
src/sections/contract/errors.ts:13 14 comment lines warn 10
src/sections/shared/roles.ts:43 13 comment lines warn 10
test/e2e/generators.ts 2634 lines warn 2560
test/e2e/generators.ts:1667 12 comment lines warn 10
test/sections/refusal-messages.test.ts:61 19 comment lines warn 10

Split the file, wrap the line, shorten or exempt the comment, or list the path in .file-size-allow.local with a # reason.

5 managed file(s) skipped; repo-platform owns them.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Stable release recovery fails when the resolved source commit predates the new npm-floor subcommand.

Review effort: Balanced
Findings: 1 High severity

Open (1)
What changed in this PR

Moves complex workflow shell logic into tested Bun scripts while preserving workflow behavior and strengthening trusted-checkout boundaries.

Changes:

  • Adds reusable workflow-step, PR-branch, auto-fix, formatting, nightly, and npm-floor logic.
  • Updates five workflows to invoke Bun subcommands.
  • Replaces shell-parsing tests with behavioral fixture tests.
File Description
.github/​scripts/​auto-fix-steps.ts Implements auto-fix rebuild and push steps.
.github/​scripts/​auto-format-steps.ts Implements formatting commit and push.
.github/​scripts/​nightly-steps.ts Implements nightly schema and type probes.
.github/​scripts/​release-pipeline.ts Adds npm version-floor enforcement.
.github/​scripts/​lib/​workflow-step.ts Adds workflow command helpers.
.github/​scripts/​lib/​pr-branch.ts Centralizes bot identity and lease pushes.
.github/​workflows/​auto-fix.yml Runs fixes from trusted Bun automation.
.github/​workflows/​auto-format.yml Runs formatting pushes from a trusted checkout.
.github/​workflows/​nightly.yml Replaces probe shell blocks with Bun commands.
.github/​workflows/​update-release-pr.yml Uses the npm-floor subcommand.
.github/​workflows/​update-release.yml Uses npm-floor for stable publishing.
test/​scripts/​step-fixture.ts Adds a workflow-step execution fixture.
test/​scripts/​auto-fix-steps.test.ts Tests rebuild, allowlist, and push behavior.
test/​scripts/​auto-format-steps.test.ts Tests formatting push behavior.
test/​scripts/​nightly-steps.test.ts Tests nightly probes and failures.
test/​scripts/​release-pipeline.test.ts Tests npm-floor outcomes.
test/​scripts/​auto-fix-allowlist.test.ts Removes superseded shell-parsing tests.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/workflows/update-release.yml Resolved
@Vivswan Vivswan added the merge-when-green Owner approved: merge once every gate is green label Oct 5, 2026
…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.
Copilot AI balanced review requested due to automatic review settings October 5, 2026 10:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

It modifies privileged write-token and package-publishing workflows, warranting final human validation despite extensive tests.

Review effort: Balanced
Findings: 1 High severity

Open (1)

…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.
Copilot AI balanced review requested due to automatic review settings October 5, 2026 10:35
@Vivswan
Vivswan marked this pull request as ready for review October 5, 2026 10:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The privileged write-token workflow and checkout-boundary changes warrant final human security review.

Review effort: Balanced
Findings: None

Resolved since last review (1)
Previously missed (2)

In code that hasn't changed since last review

Low severity Document required PR_CHECKOUT environment variable

.github/​scripts/​auto-fix-steps.ts:8

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.

Low severity Document required PR_CHECKOUT environment variable

.github/​scripts/​auto-format-steps.ts:5

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.

@Vivswan
Vivswan merged commit 5a704a3 into main Oct 5, 2026
30 checks passed
@Vivswan
Vivswan deleted the ci/shell-complexity branch October 5, 2026 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-when-green Owner approved: merge once every gate is green

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants