Skip to content

test(common): gate the release job's completions pre-merge - #1048

Closed
norrietaylor wants to merge 1 commit into
mainfrom
chore/gen-completions-helper
Closed

test(common): gate the release job's completions pre-merge#1048
norrietaylor wants to merge 1 commit into
mainfrom
chore/gen-completions-helper

Conversation

@norrietaylor

@norrietaylor norrietaylor commented Jul 29, 2026

Copy link
Copy Markdown
Member

Closes #1035.

The gap

release.yml's Generate completions step shells out to the freshly built mip / min / minimald binaries. Those invocations only ever run on a workflow_dispatch release, so a breaking CLI change clears every PR gate, merges, and sits until someone cuts a release. #1009 (completions <shell>completions print <shell>) did exactly that: twenty days later it exited 2, an hour of build time in, taking the GCS upload, the GitHub Release, and the whole stage-installer job with it. #1034 fixed the call site; the structure that let it happen stayed.

The fix

Move the generation into scripts/gen-completions.sh — one definition of what the release ships — and have both the release job and a workspace test call it. The pre-merge gate is then the same code as the release path, not a replay of it, so the two cannot drift.

release.yml  ─┐
              ├─→  scripts/gen-completions.sh
tests/        ─┘   (release artifacts / target/debug)

crates/common/tests/release_completions.rs runs the helper against target/debug and asserts every generated file registers the command it is autoloaded for. It is convention-discovered, so the always-running Linux lanes execute it through the core-tests suite with no CI edit — the extension point in docs/ci-strategy.md §10, same as scripts/lint-shell.sh (#899). just test-completions runs it directly.

Filenames are derived from the command name inside the helper rather than spelled out per row. A shell only autoloads completions from a file named for the command they complete, and the #737 binary rename left the release job writing min's completions to files named minimal — inert, exit 0, invisible until #1034. Deriving makes that mismatch unrepresentable; the test still checks the registration line, because an exit-code-only gate would not have caught it.

⚠️ One edit for a CODEOWNER: this PR is red until it lands

.github/workflows/ is frozen to agents, so the commit stops at the repo side. release_job_calls_the_generator fails by design until the step calls the helper — that failure is the gap #1035 describes, still open. Apply on this branch:

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 25f571ec..b729cb07 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -719,22 +719,17 @@ jobs:
             - name: Make binaries executable
               run: chmod +x artifacts/*-*-*
             - name: Generate completions
-              # `min` splits printing from installing (`completions print
-              # <shell>`); `mip` and `minimald` keep the flat verb. The `min`
-              # files are named for the `min` binary, not the crate — the shim
-              # it prints registers the command `min`, so a shell only autoloads
-              # it from a file of that name.
-              run: |
-                  mkdir -p artifacts/completions/{bash,zsh,fish}
-                  artifacts/mip-linux-amd64 completions bash > artifacts/completions/bash/mip
-                  artifacts/mip-linux-amd64 completions zsh  > artifacts/completions/zsh/_mip
-                  artifacts/mip-linux-amd64 completions fish > artifacts/completions/fish/mip.fish
-                  artifacts/minimal-linux-amd64 completions print bash > artifacts/completions/bash/min
-                  artifacts/minimal-linux-amd64 completions print zsh  > artifacts/completions/zsh/_min
-                  artifacts/minimal-linux-amd64 completions print fish > artifacts/completions/fish/min.fish
-                  artifacts/minimald-linux-amd64 completions bash > artifacts/completions/bash/minimald
-                  artifacts/minimald-linux-amd64 completions zsh  > artifacts/completions/zsh/_minimald
-                  artifacts/minimald-linux-amd64 completions fish > artifacts/completions/fish/minimald.fish
+              # Which binary uses which verb, and what each file is named, lives
+              # in the helper — the same code crates/common/tests/release_completions.rs
+              # runs against target/debug on every PR, so this step can no longer
+              # break unnoticed until a release is dispatched. Binaries are named
+              # here because only the workflow knows the platform-suffixed
+              # artifact names (and that `min` ships as the `minimal-*` one).
+              env:
+                  MIP_BIN: artifacts/mip-linux-amd64
+                  MIN_BIN: artifacts/minimal-linux-amd64
+                  MINIMALD_BIN: artifacts/minimald-linux-amd64
+              run: scripts/gen-completions.sh artifacts/completions
             - name: Authenticate to Google Cloud
               uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3
               with:

Or: gh pr checkout <this PR> && git apply <patch> && git commit -am 'ci(release): generate completions through scripts/gen-completions.sh'.

The step keeps the artifact names because only the workflow knows them (platform suffixes, and that min ships as the minimal-* artifact); everything that can break — verbs, filenames, the shell list — moves into reviewed code. Output tree is byte-for-byte the layout the release produces today, so completions.tar.gz and stage-release.sh are untouched.

Verification

  • Reproduces Release-job CLI invocations have no pre-merge coverage — a breaking min/mip change only surfaces on a release run #1035. Reverting the helper's min row to the pre-feat(min)!: split completions into print and install verbs #1009 flat verb fails the test with error: unrecognized subcommand 'bash'.
  • Registration check is not vacuous. Pointing min's row at a different binary fails with bash/min does not register the command `min` it is autoloaded for.
  • Release call form. MIP_BIN=… MIN_BIN=… scripts/gen-completions.sh artifacts/completions against platform-suffixed copies reproduces today's six-file tree (bash/min, zsh/_min, fish/min.fish, …); a missing binary exits 1 rather than writing a partial tree.
  • With the workflow diff applied locally, both tests pass.
  • cargo fmt --all --check, cargo clippy -p common --all-targets -- -D warnings, shellcheck, just lint-shell (25/25). minimald is skipped on macOS (Linux-only crate) and covered by the Linux lanes here.

Audit

Per the issue's ask: the completions step is the only place the release and stage-installer jobs invoke a shipped binary. The rest is gcloud, gh, tar, or a scripts/ helper that already has a harness (install.shinstall_test.sh, verify-nightly-provenance.shverify-nightly-provenance_test.sh).

Complements #687's PR9b (post-release artifact smoke) — this is the pre-merge half.

🤖 Generated with Claude Code

Note

Gate the release job's completion generation with pre-merge integration tests

  • Adds scripts/gen-completions.sh, a Bash script that generates bash/zsh/fish completion files for mip, min, and minimald, with strict error checking and support for BIN_DIR/per-binary path overrides.
  • Adds crates/common/tests/release_completions.rs with two tests: one that runs gen-completions.sh against debug binaries and asserts each output file registers its command; one that asserts the release workflow calls gen-completions.sh rather than redirecting completions inline.
  • Adds a just test-completions recipe to run the new test file in isolation.
  • Risk: the test builds missing binaries via cargo build at test time, which can significantly increase CI time on cold caches.

Macroscope summarized 6497f58.

Summary by CodeRabbit

  • New Features

    • Added automated generation of shell completion files for supported command-line tools.
    • Added completion support for Bash, Zsh, and Fish.
  • Bug Fixes

    • Improved validation to detect missing, non-executable, or empty completion outputs.
  • Tests

    • Added automated checks confirming generated completions and release workflow integration.
    • Added a convenient command for running completion checks locally.

release.yml's "Generate completions" step runs the freshly built
mip / min / minimald binaries, but only on a dispatched release — so a
breaking CLI change clears every PR gate and only surfaces an hour into
a release, taking the GCS upload, the GitHub Release and stage-installer
down with it. That is how the `completions <shell>` → `completions print
<shell>` split (#1009) shipped broken for twenty days (#1034).

Move the generation into scripts/gen-completions.sh, one definition of
what the release ships, and call it from
crates/common/tests/release_completions.rs against target/debug. The
release path and the pre-merge gate are then the same code rather than
one replaying the other, so they cannot drift, and the always-running
Linux lanes execute it through the workspace suite — the reviewed-code
extension point CI schedules over, since .github/workflows/ is frozen.
release_job_calls_the_generator holds the other half: the workflow has
to keep calling the helper instead of inlining the commands again.

The helper derives each destination filename from the command name
rather than spelling it out per row, because a shell only autoloads
completions from a file named for the command they complete. The #737
binary rename left the release job writing `min`'s completions to files
named `minimal` — inert, and invisible to any exit-code check — until
#1034. Deriving them makes that mismatch unrepresentable; the test still
asserts each generated file registers the command it is named for, which
an exit-code-only gate would not have caught.

This commit does not carry the release.yml edit that points the step at
the helper: .github/workflows/ is CODEOWNER-gated and frozen to agents,
so it is applied by hand (the diff is in the PR description). Until it
lands, release_job_calls_the_generator fails by design.

Verified: reverting the helper's `min` verb to the pre-#1009 flat form
fails the test with `unrecognized subcommand 'bash'`; pointing `min`'s
row at a different binary fails the registration check; the release
job's exact call form (MIP_BIN/MIN_BIN/MINIMALD_BIN at the
platform-suffixed artifacts) reproduces today's output tree. minimald is
left out on macOS, where it does not build, and covered by the Linux
lanes.

Refs: #1035

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Release completion generation is centralized in a shell script covering bash, zsh, and fish. Integration tests build binaries, validate generated registrations, and verify release workflow wiring. A just recipe runs the completion test with locked dependencies.

Changes

Release completion validation

Layer / File(s) Summary
Completion generator script
scripts/gen-completions.sh
Resolves binaries, generates bash, zsh, and fish completion files, supports binary overrides, and validates executable inputs and non-empty outputs.
Generated completion and workflow tests
crates/common/tests/release_completions.rs
Builds required binaries, runs the generator, verifies command registration in each shell output, and checks release workflow integration.
Local test entrypoint
justfile
Adds a locked Cargo test recipe for release completion validation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

  • gominimal/inbox#409 — Centralizes completion generation and adds pre-merge validation for release behavior.

Possibly related PRs

Suggested reviewers: msample

Poem

A rabbit watched the shells align,
Bash, zsh, and fish in a tidy line.
The binaries hopped, the tests ran bright,
Completions bloomed before release night.
“No broken carrots!” cried the hare.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The helper and tests are added, but the release workflow itself is not updated to call the helper, so the #1035 gap remains. Add the workflow change to invoke scripts/gen-completions.sh and ensure the new test reflects the updated release.yml.
✅ Passed checks (4 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The changes stay focused on completion generation, its test coverage, and a supporting just recipe.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title is concise and accurately captures the main change: gating completion generation pre-merge.
Description check ✅ Passed The description is detailed and covers the summary and verification, though it uses non-template headings and omits the checklist.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/common/tests/release_completions.rs`:
- Around line 107-108: Update the test output path setup around the
release-completions test to place the materialized output under the repository
tree instead of using std::env::temp_dir(). Preserve the process-specific
directory naming and existing cleanup via fs::remove_dir_all.
- Around line 153-163: The release workflow assertions in the relevant test must
inspect only the `Generate completions` step’s `run` block, verifying that it
invokes `scripts/gen-completions.sh` and does not inline binary invocations or
redirect to `artifacts/completions/`. Update the checks around these `assert!`
calls to parse or isolate that step before matching, so unrelated workflow
references cannot satisfy them.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b6653803-8510-46e0-a5b0-e3b3e5e5730b

📥 Commits

Reviewing files that changed from the base of the PR and between c1d466c and 6497f58.

📒 Files selected for processing (3)
  • crates/common/tests/release_completions.rs
  • justfile
  • scripts/gen-completions.sh

Comment on lines +107 to +108
let out = std::env::temp_dir().join(format!("release-completions-{}", std::process::id()));
let _ = fs::remove_dir_all(&out);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Materialize test output inside the repository tree on macOS.

temp_dir() is normally outside the checkout, contrary to the VM synchronization requirement.

Proposed fix
-    let out = std::env::temp_dir().join(format!("release-completions-{}", std::process::id()));
+    let out = root
+        .join("target")
+        .join(format!("release-completions-{}", std::process::id()));

As per coding guidelines, “On macOS, keep --output paths for materialization under the repository tree because the VM-backed CLI only synchronizes the project directory.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let out = std::env::temp_dir().join(format!("release-completions-{}", std::process::id()));
let _ = fs::remove_dir_all(&out);
let out = root
.join("target")
.join(format!("release-completions-{}", std::process::id()));
let _ = fs::remove_dir_all(&out);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/common/tests/release_completions.rs` around lines 107 - 108, Update
the test output path setup around the release-completions test to place the
materialized output under the repository tree instead of using
std::env::temp_dir(). Preserve the process-specific directory naming and
existing cleanup via fs::remove_dir_all.

Source: Coding guidelines

Comment on lines +153 to +163
assert!(
body.contains("scripts/gen-completions.sh"),
"release.yml does not call scripts/gen-completions.sh, so its completion \
invocations are unexercised until a release is dispatched — the gap #1035 \
is about. Point the \"Generate completions\" step at the helper.",
);
assert!(
!body.contains("> artifacts/completions/"),
"release.yml inlines completion invocations again; they belong in \
scripts/gen-completions.sh, which this test exercises pre-merge.",
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 12 \
  'Generate completions|gen-completions\.sh|MIP_BIN|MIN_BIN|MINIMALD_BIN|artifacts/completions' \
  .github/workflows/release.yml

Repository: gominimal/minimal

Length of output: 7483


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '\n## crates/common/tests/release_completions.rs\n'
sed -n '120,180p' crates/common/tests/release_completions.rs

printf '\n## .github/workflows/release.yml occurrences\n'
rg -n -C 4 'gen-completions\.sh|Generate completions|MIP_BIN|MIN_BIN|MINIMALD_BIN|artifacts/completions' .github/workflows/release.yml

Repository: gominimal/minimal

Length of output: 5921


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

sed -n '1,220p' crates/common/tests/release_completions.rs

Repository: gominimal/minimal

Length of output: 6897


Scope the workflow check to the Generate completions step. A stray scripts/gen-completions.sh reference or artifacts/completions/ redirect elsewhere in release.yml can satisfy these string checks without proving the step still runs the helper; match the step’s run block and its binary invocations instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/common/tests/release_completions.rs` around lines 153 - 163, The
release workflow assertions in the relevant test must inspect only the `Generate
completions` step’s `run` block, verifying that it invokes
`scripts/gen-completions.sh` and does not inline binary invocations or redirect
to `artifacts/completions/`. Update the checks around these `assert!` calls to
parse or isolate that step before matching, so unrelated workflow references
cannot satisfy them.

@norrietaylor
norrietaylor deleted the chore/gen-completions-helper branch July 29, 2026 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Release-job CLI invocations have no pre-merge coverage — a breaking min/mip change only surfaces on a release run

1 participant