From 84b5b37551db4e48d4f45aacbf8fbad6291a055d Mon Sep 17 00:00:00 2001 From: BootIntel Agent Date: Sat, 26 Sep 2026 22:50:36 +0000 Subject: [PATCH] Add a workflow to yank a crates.io version bootintel-cli 0.4.1 needs yanking: the crate was renamed to bootintel and until that old name is yanked, two names install a working `bootintel`, which is the exact ambiguity the rename removed. It could not be done from this machine. The registry token lives only as a repository secret, which is where a publish credential belongs, and GitHub secrets cannot be read back, only used inside Actions. So the yank runs where the publish ran, with the same secret. workflow_dispatch only, no defaults, so it cannot fire by accident. It takes a reason for the run log, and refuses a version that is not on the registry so a typo fails before reaching crates.io. Unyank is the same workflow with the action flipped, because yanking is reversible where publishing is not. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/crates-yank.yml | 81 +++++++++++++++++++++++++++++++ docs/releasing.md | 13 +++++ 2 files changed, 94 insertions(+) create mode 100644 .github/workflows/crates-yank.yml diff --git a/.github/workflows/crates-yank.yml b/.github/workflows/crates-yank.yml new file mode 100644 index 0000000..ce0a8b1 --- /dev/null +++ b/.github/workflows/crates-yank.yml @@ -0,0 +1,81 @@ +# Yank or un-yank a published crate version. +# +# Exists because the crates.io token lives here as a repository secret and +# nowhere else, which is correct: a publish credential should not sit on a +# laptop. That also means a yank cannot be run locally by anyone who does not +# separately hold a token, so it runs here, with the same secret the release +# workflow uses. +# +# workflow_dispatch only, with no defaults, so it cannot fire by accident. +# Yanking is reversible (`action: unyank`), unlike publishing: a version can +# be yanked and restored, but never reused. +name: crates-yank + +on: + workflow_dispatch: + inputs: + crate: + description: 'Crate name (e.g. bootintel-cli)' + required: true + type: string + version: + description: 'Exact version to act on (e.g. 0.4.1)' + required: true + type: string + action: + description: 'yank removes it from new resolution; unyank restores it' + required: true + type: choice + options: [yank, unyank] + reason: + description: 'Why, for the run log. Not sent to crates.io.' + required: true + type: string + +permissions: + contents: read + +jobs: + yank: + name: ${{ inputs.action }} ${{ inputs.crate }}@${{ inputs.version }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Require the registry token + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: | + if [ -z "${CARGO_REGISTRY_TOKEN}" ]; then + echo "::error::CARGO_REGISTRY_TOKEN is not set. See docs/releasing.md." + exit 1 + fi + + - name: Record what is being done and why + run: | + echo "action: ${{ inputs.action }}" + echo "crate: ${{ inputs.crate }}" + echo "version: ${{ inputs.version }}" + echo "reason: ${{ inputs.reason }}" + + # Refuse to touch a version that does not exist, so a typo fails here + # rather than producing a confusing registry error. + - name: Confirm the version exists on the registry + run: | + if ! cargo info "${{ inputs.crate }}@${{ inputs.version }}" >/dev/null 2>&1; then + echo "::error::${{ inputs.crate }} ${{ inputs.version }} is not on crates.io." + exit 1 + fi + + - name: ${{ inputs.action }} + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: | + if [ "${{ inputs.action }}" = "yank" ]; then + cargo yank --version "${{ inputs.version }}" "${{ inputs.crate }}" + else + cargo yank --undo --version "${{ inputs.version }}" "${{ inputs.crate }}" + fi diff --git a/docs/releasing.md b/docs/releasing.md index 4054b3d..4df96c0 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -63,6 +63,19 @@ having no brew line at all. 6. Publish the draft and mark it latest. That creates the tag on the released commit. +## Yanking a published version + +A crates.io version can be yanked, which stops new dependency resolution and +new installs reaching it, and can be restored with unyank. It can never be +reused: the number is spent whatever happens. + +The registry token lives only as a repository secret, which is correct for a +publish credential, so a yank cannot be run from a laptop that does not hold +its own token. Dispatch the `crates-yank` workflow instead. It takes the crate, +the exact version, yank or unyank, and a reason for the run log. It has no +defaults, so it cannot fire by accident, and it refuses a version that is not +on the registry so a typo fails before it reaches crates.io. + ## Publishing to crates.io Order is not optional. `bootintel` declares `bootintel-detectors` with