The observation
mint is already a CI action almost everywhere:
| step |
where it runs |
identity |
mint plan (preview) |
version.yml, workflow_call |
— |
mint attest + cosign signing |
release-provenance.yml, workflow_call |
keyless OIDC, no key material |
| JSR publish |
consumer's publish-jsr.yml, on v* tag |
OIDC |
cut + push the v* tag |
a human's laptop |
a person's git credential |
Everything downstream of the tag is already keyless and attested. The tag cut is the only step that isn't — and it's the step that starts the whole chain.
Why that's backwards
release.mjs's in-toto Statement binds tag → version plan → commit and is signed in CI by the workflow's OIDC identity. But the tag itself — the subject of that statement — is created by whoever happened to run the CLI. The provenance attests the record, not the act.
Moving the cut into a pinned workflow_dispatch job improves that rather than merely automating it. Per infra/docs/cloud-sessions.md, job_workflow_ref is load-bearing because it names reviewed, deterministic code on protected main — program attestation. A tag cut inside a pinned workflow is attested by what ran; a laptop tag is attested by who ran it.
It's also the shape the org already settled on for exactly this reason:
The correct shape is the one already in use: the session dispatches; a reviewed, pinned workflow holds the credential and does the work.
And it fits #1's direction (OIDC trusted publishing, no tokens) rather than cutting against it.
It subsumes #18's second item
#18 documents the symptom — under a ruleset restricting tag creation, git push origin v<version> is rejected even for an admin — and proposes documenting a bypass actor plus a --no-push recipe.
That's a workaround for a problem that disappears if the tag is cut by a workflow: the workflow's github.token is an installation token that can be granted tag-creation directly, and there's no standing human credential to bypass anything with. #18's item 1 (bootstrapping a first release) is orthogonal and still worth documenting.
Fresh evidence, 2026-07-31
Hit this cutting drift-gate v0.3.0. mint version and the PR merged fine; mint release did this:
mint: created signed tag v0.3.0
error: RPC failed; HTTP 403
fatal: the remote end hung up unexpectedly
Isolated rather than assumed: a branch push to the same remote succeeded seconds later, and the tag push failed identically on retry. So the refusal is specific to refs outside refs/heads/*.
Different mechanism from #18 — that's an org ruleset, this is a Claude Code session's git proxy — but the same shape: the release stalls at exactly the one step that needs a credential no automated context has. drift-gate 0.3.0 is merged, version-bumped, changelog-written, jsr publish --dry-run green, and unreleasable without someone opening a laptop.
That also blocks a downstream security fix: conformance can't clear its brace-expansion advisory until drift-gate 0.3.0 reaches JSR (ci-workflows#8).
Shape worth considering
A reusable release-cut.yml (workflow_call + consumer workflow_dispatch) that:
- verifies the manifest version has no existing tag (fail closed — never overwrite a published version)
- verifies
.release/ is empty, i.e. mint version has already been applied and merged
- re-derives the statement and checks it matches the merged commit
- creates and pushes the annotated tag with
github.token
- lets the existing
publish-jsr.yml / release-provenance.yml fire as they do today
Steps 1–3 are things the CLI checks locally today; in CI they'd be enforced rather than trusted.
Open questions
- Trigger:
workflow_dispatch (a human or a session dispatches, workflow does the work) vs. automatic on merge when .release/ has intents. Dispatch keeps a decision point in front of an immutable JSR publish, which argues for it.
- Confirmation input: require the expected version as a dispatch input and fail if it doesn't match the manifest? Cheap guard against dispatching the wrong thing.
- Tag signing: the CLI signs the tag when git signing is configured. A workflow has no signing key — but it has OIDC and cosign, and the release Statement is already the stronger attestation. Worth deciding explicitly whether the tag itself still needs a signature or whether the signed Statement supersedes it.
- Does the CLI path stay? Probably yes for local/bootstrap use (#18 item 1), but it should stop being the expected path.
One thing this must not become
cloud-sessions.md argues against giving an agent Actions OIDC, because "a workflow whose job is 'run an agent' presents an identically-shaped claim while its actual behaviour is whatever the conversation directs."
This is the opposite of that. A release-cut workflow runs pinned, deterministic code against repo state — it's the reviewed-program side of that distinction, not the agent side. Worth stating in the implementation so the two don't get conflated later.
The observation
mint is already a CI action almost everywhere:
mint plan(preview)version.yml,workflow_callmint attest+ cosign signingrelease-provenance.yml,workflow_callpublish-jsr.yml, onv*tagv*tagEverything downstream of the tag is already keyless and attested. The tag cut is the only step that isn't — and it's the step that starts the whole chain.
Why that's backwards
release.mjs's in-toto Statement bindstag → version plan → commitand is signed in CI by the workflow's OIDC identity. But the tag itself — the subject of that statement — is created by whoever happened to run the CLI. The provenance attests the record, not the act.Moving the cut into a pinned
workflow_dispatchjob improves that rather than merely automating it. Perinfra/docs/cloud-sessions.md,job_workflow_refis load-bearing because it names reviewed, deterministic code on protectedmain— program attestation. A tag cut inside a pinned workflow is attested by what ran; a laptop tag is attested by who ran it.It's also the shape the org already settled on for exactly this reason:
And it fits #1's direction (OIDC trusted publishing, no tokens) rather than cutting against it.
It subsumes #18's second item
#18 documents the symptom — under a ruleset restricting tag creation,
git push origin v<version>is rejected even for an admin — and proposes documenting a bypass actor plus a--no-pushrecipe.That's a workaround for a problem that disappears if the tag is cut by a workflow: the workflow's
github.tokenis an installation token that can be granted tag-creation directly, and there's no standing human credential to bypass anything with. #18's item 1 (bootstrapping a first release) is orthogonal and still worth documenting.Fresh evidence, 2026-07-31
Hit this cutting
drift-gatev0.3.0.mint versionand the PR merged fine;mint releasedid this:Isolated rather than assumed: a branch push to the same remote succeeded seconds later, and the tag push failed identically on retry. So the refusal is specific to refs outside
refs/heads/*.Different mechanism from #18 — that's an org ruleset, this is a Claude Code session's git proxy — but the same shape: the release stalls at exactly the one step that needs a credential no automated context has.
drift-gate0.3.0is merged, version-bumped, changelog-written,jsr publish --dry-rungreen, and unreleasable without someone opening a laptop.That also blocks a downstream security fix:
conformancecan't clear itsbrace-expansionadvisory untildrift-gate0.3.0reaches JSR (ci-workflows#8).Shape worth considering
A reusable
release-cut.yml(workflow_call+ consumerworkflow_dispatch) that:.release/is empty, i.e.mint versionhas already been applied and mergedgithub.tokenpublish-jsr.yml/release-provenance.ymlfire as they do todaySteps 1–3 are things the CLI checks locally today; in CI they'd be enforced rather than trusted.
Open questions
workflow_dispatch(a human or a session dispatches, workflow does the work) vs. automatic on merge when.release/has intents. Dispatch keeps a decision point in front of an immutable JSR publish, which argues for it.One thing this must not become
cloud-sessions.mdargues against giving an agent Actions OIDC, because "a workflow whose job is 'run an agent' presents an identically-shaped claim while its actual behaviour is whatever the conversation directs."This is the opposite of that. A release-cut workflow runs pinned, deterministic code against repo state — it's the reviewed-program side of that distinction, not the agent side. Worth stating in the implementation so the two don't get conflated later.