Releases are cut from the publish workflow in GitHub Actions. There is no
local release script, no automated release on push, and no changesets tool.
Every release is a manual workflow_dispatch.
Committer identity: post-publish commits to
mainare made by thekilo-maintainerGitHub App (not the genericgithub-actions[bot]), using a short-lived installation token minted inside the workflow. The app is the sole principal onmain's branch-ruleset bypass list — see Branch protection. If you see a release commit authored by anyone else, something's wrong; stop and investigate.Required secrets (already configured, documented here for disaster-recovery reference):
KILO_MAINTAINER_APP_ID— the numeric App ID.KILO_MAINTAINER_APP_SECRET— the App's private key (full PEM).Both are consumed by
.github/actions/setup-git-committer/action.yml. The same pair is used inKilo-Org/kilocode; regenerating the private key there would require updating it here too.
There are exactly two channels and they correspond to npm dist-tags:
| Channel | npm dist-tag | Purpose | Version format |
|---|---|---|---|
latest |
latest |
Public stable releases. The default npm install. |
X.Y.Z |
dev |
dev |
Internal dogfood snapshots. npm install …@dev. |
X.Y.Z-dev.N |
That's the entire surface. There is no beta, rc, next, or canary.
Before clicking "Run workflow", confirm:
-
mainis green on all three CI workflows (typecheck,test,format). -
CHANGELOG.mdhas the changes you're about to ship listed under## [Unreleased]. - You know which channel you're targeting and which inputs you'll use (see paths below).
- The tag for the resulting version does not already exist on https://github.com/Kilo-Org/shell-security/releases. The workflow fails fast if it does, but check first — it's cheaper to pick a different bump than to recover from a partial publish.
- Open https://github.com/Kilo-Org/shell-security/actions/workflows/publish.yml
- Click Run workflow (top right).
- Fill in the inputs — see paths below.
- Click Run workflow.
- Wait for the job to finish (typically 2–3 minutes).
- Verify on npm that the new version shipped with the right dist-tag.
- Verify on the GitHub releases page that the tag and release were created.
For public releases that go to npm install @kilocode/shell-security.
Auto-bump (the common path):
| Input | Value |
|---|---|
channel |
latest |
bump |
patch (or minor / major) |
version |
(leave blank) |
The workflow queries the highest existing vX.Y.Z tag, bumps it, and
publishes. Example: highest stable is v1.2.3, you pick bump=patch,
the new version is 1.2.4.
Explicit version (rare):
| Input | Value |
|---|---|
channel |
latest |
bump |
(leave blank) |
version |
1.2.5 |
Use this only when you need to skip a number or seed the very first stable
release (since auto-bump from a fresh repo would resolve to 0.0.1, which
is rarely what you want for 1.0.0).
For internal dogfood builds. Versions look like 0.1.0-dev.1,
0.1.0-dev.2, etc. They publish to the dev npm dist-tag, so users get
them with npm install @kilocode/shell-security@dev.
Continue current dev cycle (the common path):
| Input | Value |
|---|---|
channel |
dev |
bump |
(leave blank) |
version |
(leave blank) |
The workflow queries the highest existing *-dev.N tag and increments
the counter. Example: highest dev is 0.1.0-dev.5, the new version is
0.1.0-dev.6. Same 0.1.0 base — only the dev counter moves.
Start a new dev cycle (after a stable release):
| Input | Value |
|---|---|
channel |
dev |
bump |
patch (or minor / major) |
version |
(leave blank) |
The workflow takes the highest stable, applies the bump, and seeds
dev.1. Example: stable is 0.1.0, you pick bump=minor, the new
version is 0.2.0-dev.1. Use this when you've shipped a stable release
and want to start the next dev cycle.
Explicit version (one-off):
| Input | Value |
|---|---|
channel |
dev |
bump |
(leave blank) |
version |
0.3.0-dev.1 |
Format must match X.Y.Z-dev.N exactly. Use this for the very first dev
cut (since auto-bump from a fresh repo seeds at 0.0.1-dev.1, which is
rarely the version you actually want), or to manually skip ahead.
- Move the
[Unreleased]entries inCHANGELOG.mdinto a new## [X.Y.Z] - YYYY-MM-DDsection. - Add a compare-link at the bottom of the file.
- Commit these changes to
mainthrough a normal PR.
The publish workflow does not touch CHANGELOG.md. It only bumps
package.json.
For dev releases, do not update CHANGELOG.md — dev snapshots are
ephemeral and the changelog tracks user-facing stable releases only.
| Channel | Commits version bump to main? |
Pushes git tag? | Creates GitHub release? |
|---|---|---|---|
latest |
Yes | Yes | Yes |
dev |
No (orphan commit + tag) | Yes | Yes (marked --prerelease) |
Dev releases create a tag pointing at an orphan commit (the package.json
bump made in the CI runner). The orphan commit is reachable through the
tag but is not on any branch, so main history stays clean. This is
intentional — dev publishes happen frequently, and committing every
release: v0.1.0-dev.N back to main would be noise.
The workflow's steps run in this order: install → typecheck/test/format check → verify npm auth → resolve version → publish to npm → verify publish landed → commit/tag/release. Failures get progressively more dangerous the further down the list they happen, because side effects accumulate. Recovery procedure depends on which step failed.
Includes: install, typecheck, test, format check, verify-npm-auth, and
resolve-version steps. Symptoms: bun install errors, type errors, test
failures, prettier complaints, npm whoami errors, version.ts validation
errors.
Recovery: none required. Nothing was published, nothing was committed, nothing was tagged. Just fix the underlying problem and re-dispatch the workflow.
The most common subtype here is bad or missing NPM_TOKEN, surfaced
by the verify-npm-auth step:
npm error code ENEEDAUTH
npm error need auth This command requires you to be logged in.
Fix: add or update the NPM_TOKEN secret in repo settings (see
AGENTS.md for token requirements), then
re-dispatch. Nothing else to clean up.
Symptom: npm publish reported success, but the post-publish
"Verify publish landed on registry" step fails after 3 retries with:
::error::npm publish reported success but VERSION did not appear on the registry after 3 attempts
Most likely cause: registry replication lag or a transient registry
issue. The version IS published — npm view just isn't seeing it from
the runner's resolved registry mirror yet.
Recovery:
-
Wait 1–2 minutes, then verify manually from your machine:
npm view @kilocode/shell-security@VERSION version
-
If the version IS on npm now, the publish was real. Manually create the tag and GitHub release per Scenario 4 below — but only the tag/release portion, not the npm publish portion.
-
If the version is NOT on npm after 5 minutes, the publish actually failed and you can re-dispatch. (This case is rare;
npm publishstrongly tries not to lie.)
Symptom: npm has the new version, the git tag is pushed and visible on
GitHub, but the "Create GitHub release" step failed with a gh
error (rate limit, transient API error, missing permissions).
This leaves a "headless" tag — version is on npm, tag exists on GitHub, but the GitHub releases page doesn't list the new version.
Recovery: create the GitHub release manually from your machine.
# For stable releases:
gh release create vX.Y.Z \
--repo Kilo-Org/shell-security \
--title "vX.Y.Z" \
--generate-notes
# For dev releases (note --prerelease):
gh release create vX.Y.Z-dev.N \
--repo Kilo-Org/shell-security \
--title "vX.Y.Z-dev.N" \
--generate-notes \
--prereleaseThe next dispatch will succeed normally because version.ts's tag-exists
precheck looks at GitHub releases — and the manual gh release create
above creates one.
This is the most dangerous failure mode. Symptom: npm publish succeeds
(package is live on npm at the new version) and the verify-publish-landed
step passes, but the workflow fails at the "Commit version bump (stable
only)" or "Tag release" step with a remote rejected error.
Most common cause: main's branch ruleset bypass list does not include
the kilo-maintainer GitHub App, or the App's installation token is
expired/revoked. See Branch protection below.
Recovery steps:
-
Do not re-run the workflow. The package is already published; a rerun will fail at the tag-exists precheck (after another version is resolved) or at the verify-publish-landed step.
-
For stable releases, create the version bump + tag locally and push them:
git checkout main git pull # Bump package.json manually to the version that was published. git add package.json git commit -m "release: v1.2.4" git tag v1.2.4 git push origin main --tags
-
For dev releases, create just the tag pointing at an orphan commit:
git checkout --detach # Bump package.json manually to the version that was published. git add package.json git commit -m "release: v0.1.0-dev.6" git tag v0.1.0-dev.6 git push origin v0.1.0-dev.6 git checkout main # IMPORTANT: get back to a real branch before # doing anything else, or your next git operation # will be from detached HEAD and may be lost.
-
Create the GitHub release manually (same as Scenario 3 above):
gh release create v1.2.4 \ --repo Kilo-Org/shell-security \ --title "v1.2.4" \ --generate-notes # Add --prerelease for dev releases.
-
Verify the App token, App installation, and bypass list are still valid (see Branch protection below) before the next release.
The Main branch protection ruleset on main (Settings → Rules →
Rulesets) restricts direct pushes. The publish workflow bypasses this
restriction by authenticating as the kilo-maintainer GitHub App rather
than the default github-actions[bot]. Requirements for this to work:
kilo-maintainerApp is installed onKilo-Org/shell-security. Verify at Settings → GitHub Apps → Installed GitHub Apps. The App is also installed onKilo-Org/kilocode; same App, same credentials.- Two secrets exist in this repo (or at org level, exposed to this
repo):
KILO_MAINTAINER_APP_ID(numeric) andKILO_MAINTAINER_APP_SECRET(full PEM private key). Regenerating the App's private key requires updatingKILO_MAINTAINER_APP_SECRETin every repo that uses it. - Bypass actor configured on the ruleset. Settings → Rules →
Main branch protection→ Bypass list must include thekilo-maintainerApp with bypass modeAlways. Do NOT add the genericgithub-actionsapp — that would grant bypass to every workflow in the repo; the point of using the dedicated app is to keep the bypass narrowly scoped to the publish flow.
If a stable publish fails at the push step and all three above are in
place, check the workflow run's Setup git committer step output — a
revoked installation or an expired/rotated private key would fail there
rather than at the push.
Dev-channel publishes don't touch main (they push only the tag via an
orphan commit), so they don't depend on item 3 above — but they still
need items 1 and 2 for the same token minting flow.
When this applies: the very first publish of a package slug that
doesn't exist on npm yet. Happens once at package creation, and again
if the package is ever renamed (as when @kilocode/openclaw-security-advisor
became @kilocode/shell-security).
The chicken-and-egg: npm trusted publishers (OIDC) can only be
configured on a package that already exists on the registry. Until the
package slug exists, there's nothing to attach trust to. So the very
first publish must use a classic npm token, not OIDC. The workflow's
OIDC-based publish step will fail with 401 Unauthorized or similar.
-
Get an npm classic automation token with publish permission for the
@kilocodescope (npmjs.com → avatar → Access Tokens → Generate New Token → "Automation" or "Publish"). -
Publish locally from a clean checkout of the main branch:
git checkout main && git pull # Edit package.json: remove "private": true AND set "version" to the # target, e.g. "0.2.0". Do NOT commit this — it's just for the local # publish. NPM_CONFIG_PROVENANCE=false npm publish --tag latest --access public \ --//registry.npmjs.org/:_authToken=$YOUR_CLASSIC_TOKEN # Restore private: true and version locally; discard the edit.
Provenance must be off on this step — provenance attestation requires OIDC, which is exactly what we don't have yet.
-
Verify on npm:
npm view @kilocode/shell-security versionshould return the version you just published. -
Create the git tag and GitHub release by hand so future
script/version.tsruns see it:git tag v0.2.0 -m "Release v0.2.0" git push origin v0.2.0 gh release create v0.2.0 --title v0.2.0 --generate-notes --verify-tag
Once the package slug exists:
- On npmjs.com, navigate to the package settings → Trusted Publishers.
- Add a GitHub Actions publisher:
- Repository owner:
Kilo-Org - Repository name:
shell-security - Workflow file:
publish.yml - Environment: (leave blank)
- Repository owner:
- Save.
From the second release onward, the normal workflow_dispatch flow in
this document applies — the workflow authenticates to npm via OIDC,
publishes with provenance, and handles git/GitHub-release side effects.