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
A release now takes two human actions, both tag pushes, and one host.
Action
Tag
What runs
Cut a candidate
rc-0.6.0-1 on a commit of main
deploy-candidate.yml: verify (the tag names the version every Cargo.toml carries, the commit is on main), deploy to staging, publish a GitHub prerelease carrying the deployed binary
Promote
v0.6.0 on the same commit
publish-crate.yml publishes imcp2-core and imcp2 to crates.io; v-release.yml (dist) ships imcp2-local's archives, installers and Claude Desktop bundle on the v0.6.0 release page
Nothing in this repository deploys production: it embeds the published crate. Staging moves only on candidates, never on a merge to main, so the revision under test stays put while it is tested. A failed candidate is followed by a fix on main and rc-0.6.0-2; nothing is bumped after a candidate, so the promoted commit is the tested one. publish-crate.yml refuses a commit unless an rc-X.Y.Z-N tag on it has its prerelease with the deployed binary, which exists only if that candidate's staging deploy succeeded (a v tag pushed too early fails and is re-run once the deploy is done).
Why rc- rather than v0.6.0-rc.1: dist matches a tag's version against Cargo.toml exactly, so a prerelease suffix that the manifests do not carry fails its plan (verified locally with dist 0.31.0), and both dist's and the crate workflow's v triggers would otherwise catch candidates. With tag-namespace = "v", dist's trigger matches v0.6.0 and ignores rc-*, release-* and the old imcp2-local-v* tags; unset, it would also match release-2026-09-24-0.5.0 and fail to parse it. The regenerated workflow differs from the old one in its trigger pattern alone. dist's workflow is not gated on the candidate (it cannot be without hand-editing the generated file); the crate publish, the irreversible step, is.
Related issues
Follows the discussion after #203. Replaces the release-* flow, which no longer described what happens (production is deployed from the gateway repository).
Changes
.github/workflows/deploy-candidate.yml (new) — rc-* tags and a rollback dispatch (an earlier rc-* tag or a full SHA, validated as before). Adds a verify job; the deploy and the release-page publish are the ones deploy-release.yml had, the page now a prerelease and its binary the marker promotion checks. Environment staging, the DEPLOY_* secrets.
.github/workflows/publish-crate.yml — header describes promotion; the version check covers imcp2-local too; new step "Check the tag promotes a deployed candidate" (reads the candidate's prerelease with the workflow token; the verify job's permissions are unchanged); the release environment gate is unchanged.
dist-workspace.toml — tag-namespace = "v"; .github/workflows/imcp2-local-release.yml regenerated as v-release.yml (trigger pattern is the only difference).
.github/scripts/build-mcpb.sh, imcp2-local-mcpb.yml, imcp2-local-install-note.yml — accept vX.Y.Z tags and name v-release.yml as the attestation signer.
deploy-native.yml — header names its one caller.
Docs: CONTRIBUTING.md ("Releasing" rewritten as the two-step flow), deploy/native/README.md (one host, candidates, promotion, rollback; secrets table without the production column; approval-gate section), crates/imcp2-local/README.md (releases/latest resolves directly now that candidates are prereleases; signer workflow; maintainers' section; a note on installing the pre-0.6.0 imcp2-local-v* releases, which latest does not resolve to and which imcp2-local-release.yml attested), README.md, SECURITY.md, docs/anthropic-directory-submission.md, crates/imcp2-local/Cargo.toml comment.
Repository settings to change alongside (not code):
Extend the protected-tag ruleset to rc-* (it covers v*; imcp2-local-v* can drop out).
The production environment and the PROD_* secrets are no longer read by any workflow and can be deleted.
Until the first v0.6.0 release exists, releases/latest still points at the newest release-* page. Marking the old release-* releases as prereleases, or waiting for 0.6.0, fixes that; the README's note covers installing 0.5.0 meanwhile.
crates.io trusted publishing is untouched: publish-crate.yml keeps its name and environment.
First version under the scheme: 0.6.0, cut by a version-bump PR, then rc-0.6.0-1.
Testing
dist generate from the new config; diff against the previous generated workflow is the trigger pattern only
dist plan --tag v0.5.0 passes on the new config; dist plan --tag rc-0.5.0-1 fails, as intended and as the trigger prevents
Every workflow file parses as YAML; bash -n on the bundle script
The promotion check run against a stubbed gh and a repository with three candidate tags: a commit with one undeployed and one deployed rc-0.6.0-N promotes; a commit whose only candidate has no prerelease, or has candidates for another version only, is refused
.github/scripts/scan-internal-identifiers.sh origin/main...HEAD — clean (re-run on bf66af4)
CI green on 96564ae (test, End-to-end, Formatting, Scan, dist plan, bot policies); bf66af4 changes one README sentence
No Rust or dashboard code changes; cargo test and npm test unaffected.
Not exercised here: an actual rc-* push against staging. The first candidate will be the live test of deploy-candidate.yml; its deploy and publish jobs are the previous workflow's, moved.
Review rounds (Copilot): round 1, on 6206f2c, found that the promotion check proved only that a candidate tag existed (fixed in 96564ae: the candidate's prerelease with the deployed binary is required) and that the local crate's install instructions did not fit the pre-0.6.0 releases (fixed in 96564ae: a note on installing those). Round 2, on 96564ae, posted no findings; its summary noted the local crate's README said the version check runs "before the tag can exist", which a tag-triggered workflow cannot do (fixed in bf66af4: before the staging deploy and the prerelease).
…s and the local MCP
Two human actions now cut a release, both tag pushes. An `rc-X.Y.Z-N` tag on
a commit of main deploys that commit to staging and publishes a GitHub
prerelease carrying the deployed binary; the matching `vX.Y.Z` tag on the same
commit publishes imcp2-core and imcp2 to crates.io and runs dist, which ships
imcp2-local's binaries, installers and Claude Desktop bundle on the `vX.Y.Z`
release page. Nothing in this repository deploys production: it embeds the
published crate.
Staging therefore moves only on candidates, never on a merge to main, so the
revision under test stays put while it is tested. deploy.yml (main -> staging)
and deploy-release.yml (release-* -> the host once called production) are
replaced by deploy-candidate.yml, which keeps the rollback dispatch and the
release-page publish (as a prerelease) and adds a verify job: the tag must
name the version every Cargo.toml carries, and the commit must be on main.
publish-crate.yml checks imcp2-local's version too and refuses a commit that
no rc-X.Y.Z-N tag points at, so a release is always a tested candidate.
dist's tag namespace becomes `v`, so plain version tags trigger it and
candidate tags do not (dist matches a tag's version against Cargo.toml exactly
and would fail on a candidate); the generated workflow is regenerated as
v-release.yml, and the bundle script and the attestation instructions name it.
With candidates as prereleases, GitHub's `latest` release is always a promoted
version, so the local crate's README resolves it directly instead of paging
through the release list.
CONTRIBUTING.md, the deploy README, SECURITY.md, the top-level README and the
directory-submission notes describe the flow; the deploy README drops the
production column from its secrets table and the `PROD_*` set can be deleted.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xd7VT72Qt16qiAJynu9EKj
…-0.6.0 releases
Copilot's review of the candidate flow, two findings:
The crate publish accepted any commit an rc-X.Y.Z-N tag pointed at, which
proves a tag was pushed, not that deploy-candidate.yml succeeded (or had
finished). It now requires that candidate's prerelease to carry the deployed
binary, which the candidate workflow uploads only after the staging deploy
succeeded; a v tag pushed too early fails with instructions to re-run once
the deploy is done. The step reads the release with the workflow's own
token, so the verify job's permissions are unchanged. The candidate
workflow's comment names the asset as the marker the check depends on, and
CONTRIBUTING.md and the deploy README describe the rule as it now is.
The local crate's README resolved `latest` and named v-release.yml as the
attestation signer, neither of which fits a release cut before 0.6.0
(tagged imcp2-local-vX.Y.Z, attested by imcp2-local-release.yml). A note
says how to install one of those.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xd7VT72Qt16qiAJynu9EKj
The candidate workflow runs only after the rc-* tag is pushed, so it cannot check the version “before the tag can exist”; an invalid tag still exists even though its deployment fails. Reword this to say the check happens before staging deployment/prerelease publication.
The local crate's README said the candidate step checks the version
"before the tag can exist"; a workflow runs after the tag is pushed, so the
check comes before the staging deploy and the prerelease, which is what
keeps a mismatched candidate from being promoted. Copilot's second round.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xd7VT72Qt16qiAJynu9EKj
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.
Summary
A release now takes two human actions, both tag pushes, and one host.
rc-0.6.0-1on a commit ofmaindeploy-candidate.yml: verify (the tag names the version everyCargo.tomlcarries, the commit is onmain), deploy to staging, publish a GitHub prerelease carrying the deployed binaryv0.6.0on the same commitpublish-crate.ymlpublishesimcp2-coreandimcp2to crates.io;v-release.yml(dist) shipsimcp2-local's archives, installers and Claude Desktop bundle on thev0.6.0release pageNothing in this repository deploys production: it embeds the published crate. Staging moves only on candidates, never on a merge to
main, so the revision under test stays put while it is tested. A failed candidate is followed by a fix onmainandrc-0.6.0-2; nothing is bumped after a candidate, so the promoted commit is the tested one.publish-crate.ymlrefuses a commit unless anrc-X.Y.Z-Ntag on it has its prerelease with the deployed binary, which exists only if that candidate's staging deploy succeeded (avtag pushed too early fails and is re-run once the deploy is done).Why
rc-rather thanv0.6.0-rc.1: dist matches a tag's version againstCargo.tomlexactly, so a prerelease suffix that the manifests do not carry fails its plan (verified locally with dist 0.31.0), and both dist's and the crate workflow'svtriggers would otherwise catch candidates. Withtag-namespace = "v", dist's trigger matchesv0.6.0and ignoresrc-*,release-*and the oldimcp2-local-v*tags; unset, it would also matchrelease-2026-09-24-0.5.0and fail to parse it. The regenerated workflow differs from the old one in its trigger pattern alone. dist's workflow is not gated on the candidate (it cannot be without hand-editing the generated file); the crate publish, the irreversible step, is.Related issues
Follows the discussion after #203. Replaces the
release-*flow, which no longer described what happens (production is deployed from the gateway repository).Changes
.github/workflows/deploy-candidate.yml(new) —rc-*tags and a rollback dispatch (an earlierrc-*tag or a full SHA, validated as before). Adds averifyjob; the deploy and the release-page publish are the onesdeploy-release.ymlhad, the page now a prerelease and its binary the marker promotion checks. Environmentstaging, theDEPLOY_*secrets..github/workflows/deploy.yml,.github/workflows/deploy-release.yml— removed..github/workflows/publish-crate.yml— header describes promotion; the version check coversimcp2-localtoo; new step "Check the tag promotes a deployed candidate" (reads the candidate's prerelease with the workflow token; the verify job's permissions are unchanged); thereleaseenvironment gate is unchanged.dist-workspace.toml—tag-namespace = "v";.github/workflows/imcp2-local-release.ymlregenerated asv-release.yml(trigger pattern is the only difference)..github/scripts/build-mcpb.sh,imcp2-local-mcpb.yml,imcp2-local-install-note.yml— acceptvX.Y.Ztags and namev-release.ymlas the attestation signer.deploy-native.yml— header names its one caller.CONTRIBUTING.md("Releasing" rewritten as the two-step flow),deploy/native/README.md(one host, candidates, promotion, rollback; secrets table without the production column; approval-gate section),crates/imcp2-local/README.md(releases/latestresolves directly now that candidates are prereleases; signer workflow; maintainers' section; a note on installing the pre-0.6.0imcp2-local-v*releases, whichlatestdoes not resolve to and whichimcp2-local-release.ymlattested),README.md,SECURITY.md,docs/anthropic-directory-submission.md,crates/imcp2-local/Cargo.tomlcomment.Repository settings to change alongside (not code):
rc-*(it coversv*;imcp2-local-v*can drop out).productionenvironment and thePROD_*secrets are no longer read by any workflow and can be deleted.v0.6.0release exists,releases/lateststill points at the newestrelease-*page. Marking the oldrelease-*releases as prereleases, or waiting for 0.6.0, fixes that; the README's note covers installing 0.5.0 meanwhile.publish-crate.ymlkeeps its name and environment.First version under the scheme: 0.6.0, cut by a version-bump PR, then
rc-0.6.0-1.Testing
dist generatefrom the new config; diff against the previous generated workflow is the trigger pattern onlydist plan --tag v0.5.0passes on the new config;dist plan --tag rc-0.5.0-1fails, as intended and as the trigger preventsbash -non the bundle scriptghand a repository with three candidate tags: a commit with one undeployed and one deployedrc-0.6.0-Npromotes; a commit whose only candidate has no prerelease, or has candidates for another version only, is refused.github/scripts/scan-internal-identifiers.sh origin/main...HEAD— clean (re-run on bf66af4)cargo testandnpm testunaffected.Not exercised here: an actual
rc-*push against staging. The first candidate will be the live test ofdeploy-candidate.yml; its deploy and publish jobs are the previous workflow's, moved.Review rounds (Copilot): round 1, on 6206f2c, found that the promotion check proved only that a candidate tag existed (fixed in 96564ae: the candidate's prerelease with the deployed binary is required) and that the local crate's install instructions did not fit the pre-0.6.0 releases (fixed in 96564ae: a note on installing those). Round 2, on 96564ae, posted no findings; its summary noted the local crate's README said the version check runs "before the tag can exist", which a tag-triggered workflow cannot do (fixed in bf66af4: before the staging deploy and the prerelease).
Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_01Xd7VT72Qt16qiAJynu9EKj