Version bump, changelog, tag, push release, and mark the Sentry issues it fixes
Scanned 9/22/2026
Install to Claude Code
npx -y skills add Kangentic/kangentic --skill release --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Release?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/kangentic-release)More formats (shields.io, HTML) on the badges page.
---
description: Version bump, changelog, tag, push release, and mark the Sentry issues it fixes
allowed-tools: Read, Glob, Grep, Edit, Write, Bash(git:*), Bash(gh:*), Bash(npm:*), Bash(npx:*), Bash(curl:*), PowerShell, Agent
argument-hint: [patch|minor|major]
---
# Release
Release pipeline: version bump, changelog generation, git tag, and push to trigger the release workflow.
**Usage:** `/release [patch|minor|major]`
- `/release` -- derives the bump type from commit history and applies it, stopping only for a major
- `/release patch` -- bump 0.1.0 to 0.1.1
- `/release minor` -- bump 0.1.0 to 0.2.0
- `/release major` -- bump 0.1.0 to 1.0.0
**Release type (optional):** $ARGUMENTS
This command does NOT use `/merge-back`. The release flow is fundamentally different: no rebase, creates tags, and pushes to main directly.
## Where this runs
Every step operates on the MAIN checkout, even though the board usually hands the release task its
own worktree. Git is the easy half: pass `git -C <main> ...` everywhere. The cwd-sensitive commands
are the trap, because a bare `cd` is reset by the harness between tool calls. Run each of
`node scripts/verify-node-modules.js`, `npm ci`, `npm run typecheck`, `npx playwright test`, and
`npm version` as one PowerShell call shaped `Push-Location <main>; <command>`. `npm ci` is the one
that does damage rather than nothing: run it bare and it wipes the WORKTREE's `node_modules` while
the verifier in main still reports stale. Every Read, Glob, Grep, Edit, and Write path is a
main-checkout absolute path, and the `doc-auditor` agent is given the main-checkout root so it does
not audit a worktree copy of the repo.
The version bump is where getting this wrong is silent. An `npm version` that runs in the worktree
bumps the worktree's `package.json`, stages nothing in main, and ships a `vX.Y.Z` tag on the
PREVIOUS version. That is why Step 2 reads all three files back.
## Step 0 -- Determine Bump Type
1. **Find the previous tag:** Run `git describe --tags --abbrev=0 --match "v*"`. Note whether this
succeeds or fails (no tags = first release).
**`--match "v*"` is load-bearing, not decoration.** `@kangentic/protocol` ships on its own
cadence from this same branch, so a `protocol-v*` tag is often the most recent reachable one.
Without the filter this returns that protocol tag, and both this step and Step 3 then read a
range that starts in the middle of the desktop release. Releasing v0.41.0 hit exactly this:
the bare command returned `protocol-v0.14.0`, and three protocol releases had landed inside
the real v0.40.0..v0.41.0 range. Nothing about the truncated output looks wrong, which is the
whole problem. The changelog comes out short but plausible, and the bump derivation reads the
same truncated range, so a minor can silently derive as a patch.
2. **Collect commits since last tag:** Run `git log <previousTag>..HEAD --oneline --no-decorate` (or `git log --oneline --no-decorate` if no previous tag).
3. **Analyze conventional commit prefixes to derive a bump type:**
- Any commit with `!` after the type (e.g., `feat!:`, `fix!:`) or containing `BREAKING CHANGE` in the subject -- suggest **major**
- Any `feat:` commit -- suggest **minor**
- Only `fix:`, `chore:`, `docs:`, `refactor:`, `test:`, `style:`, `perf:`, `ci:`, `build:` -- suggest **patch**
- If no conventional prefixes found, fall back to keyword analysis (same as legacy): "Add"/"Implement"/"Create" = minor, "Fix" = patch, otherwise patch
4. **If `$ARGUMENTS` is `patch`, `minor`, or `major`:** use it directly and skip the derivation in point 3.
5. **First-release check:** If no previous tags exist and `$ARGUMENTS` is empty, read the current version from `package.json`, report "No previous releases found, releasing the current version as v{version}", and skip the version bump in Step 2 (tag the current version as-is). Do not ask. This branch is unreachable on Kangentic, which has released since v0.1.0; it exists for a fresh fork.
6. **Otherwise (no explicit argument): apply the derived bump and keep going.** Do not ask. Report
what it derived and why, then proceed:
```
Bump: minor (0.1.0 -> 0.2.0)
Reason: 3 feat: commits, no breaking changes, since v0.1.0
Commits: feat: add dark mode, feat: add notifications, fix: resolve crash
```
The derivation in point 3 is deterministic, and the operator can force the answer any time by
passing `/release patch|minor|major`, so a confirmation gate here only re-asks a question the
commit log already answered. The gate also has a worse record than the rule. v0.40.0 was first
tagged as a minor over a range carrying no `feat:` commits, where the rule alone would have said
patch. Unwinding that took a tag deletion, a remote tag deletion, a draft release deletion, and
a retarget to v0.39.1.
**The one exception is major.** If the range contains a `!` commit or a `BREAKING CHANGE`
subject, stop and report the specific commits rather than tagging it. A major is rare, it is the
one bump a human almost always wants to weigh in on, and on a 0.x line it is the jump to 1.0.0.
## Pre-flight Checks
1. **Verify branch:** Run `git rev-parse --abbrev-ref HEAD`. Must be `main`. If not, stop with an error: "Release must run from the main branch."
2. **Verify clean tree:** Run `git status --porcelain`. Must be empty. If not, stop with an error: "Working tree must be clean before releasing. Commit or stash changes first."
3. **Fetch latest:** Run `git fetch origin main`
4. **Verify up-to-date:** Run `git diff HEAD origin/main --stat`. Must be empty. If not, stop with: "Local main is behind origin/main. Run `git pull` first."
5. **Verify dependencies:** Run `node scripts/verify-node-modules.js`. It compares
`node_modules/.package-lock.json` against `package-lock.json` and prints which way it went.
Act on its exit code with no judgment call and no prompt to the user:
- **Exit 0:** the tree already matches the lockfile. Nothing to install. Continue.
- **Exit 1 or 2:** the tree is stale or was never installed. Run `npm ci`, then re-run the
verifier. If `npm ci` fails with EBUSY, stop with: "A file in node_modules is locked by a
running process. Close the Kangentic dev server (`npm start`) and retry."
Do not run `npm ci` unconditionally. It deletes `node_modules`, and the team dogfoods
Kangentic from `npm start`, so on most releases the live dev server is running Electron out of
the directory `npm ci` is about to remove. That made the step either fail with EBUSY or break
the dev server, and turned a gate into a question the operator had to answer mid-release. The
verifier answers the same question in milliseconds and writes nothing.
6. **Verify the Sentry symbol-upload secret:** Run `gh secret list --repo Kangentic/kangentic`.
`KANGENTIC_SENTRY_TOKEN` must be listed. If it is not, stop with: "KANGENTIC_SENTRY_TOKEN is
not set on the repo, so this release would ship with no sourcemaps and no native debug files.
Add it as a repository secret, then re-run."
This checks the GITHUB secret on purpose, not a local environment variable. Release builds run
only on the CI matrix, so a local `KANGENTIC_SENTRY_TOKEN` says nothing about what the runners
will see. The release workflow makes the same check in its `preflight-symbols` job, but on the
normal tag-push path the tag already exists by the time that job runs. This step is the only
one that can stop the tag from being created at all.
7. **Verify the target version is unused.** A number freed by a cancelled run reads exactly like a
fresh one, one of the four surfaces cannot be undone, and one of them CI will not catch for you.
Check all four against the version Step 0 settled on:
- `git tag --list "vX.Y.Z"` (local tag)
- `git ls-remote --tags origin "refs/tags/vX.Y.Z"` (remote tag)
- `gh release list --repo Kangentic/kangentic --limit 100` (a surviving release, draft or published)
- `npm view kangentic versions --json` (npm)
A hit on any of the first three stops the release until that leftover is deleted. `--limit 100`
is not decoration: the default page of 30 no longer covers this repo's history, so a leftover
for an older number can fall off it.
A hit on npm is terminal for that number. A published npm version can never be republished, so
the release takes the next free one, which means Step 2 runs the explicit
`npm version <the next free version> --no-git-tag-version` rather than a bump keyword, which
would recompute the taken number from `package.json` and land on it again.
A leftover DRAFT is the surface CI will not catch. `create-draft-release` treats an existing
draft for the tag as a resumable one, logs "A draft release already exists for $tag; reusing
it", and exits 0, so all three platform builds then upload into whatever a cancelled run left
behind. It fast-fails only on a release that is already PUBLISHED and incomplete, which is the
state Step 7 describes. So this check, not the workflow, is what stands between a stale draft
and a published release built on top of it.
Reach for this whenever the target version already appears in `git log`. v0.40.0 was tagged,
cancelled before any platform job uploaded, and torn down; v0.39.1's commit message recorded the
number as free, and v0.40.0 later shipped normally on it. That declaration lived in a commit
message and was not self-verifying, so these four checks are what make a reuse safe.
8. **Verify CI is green on the commit being released.** Run
`gh run list --repo Kangentic/kangentic --workflow=ci.yml --branch main --limit 1 --json headSha,status,conclusion`
and compare `headSha` against `git rev-parse HEAD`. The `--json` fields are the point: the
default table prints no SHA, so without them the step cannot check the half that matters.
Require `conclusion: success` AND `headSha` equal to HEAD. On `failure`, stop: the code does
not pass its own gate. If `status` is not `completed`, wait for it. If `headSha` is not HEAD,
the last push never triggered CI and there is nothing here to trust, so say that rather than
reading a stale run as a pass.
This is the authoritative gate, and it is strictly broader than Step 1's local run. CI runs
lint, typecheck, build, 3 unit shards, 11 UI shards, and 5 Linux Electron E2E shards on this
exact commit. Step 1 runs typecheck and the UI tier, and nothing else: a lint error, a unit
regression, a broken build, or an E2E failure would all sail past it. One `gh` call covers
every one of those in about a second.
Confirm the green is a real green, not a green-via-retry. CI runs UI and E2E with `retries: 1`,
so a flake hides inside a passing check. Run
`gh run view <runId> --repo Kangentic/kangentic --log` as ONE command and read `flaky` out of
the output it returns. Do not pipe it into a filter and do not redirect it to a file:
`bash-single-command.md` forbids both, the guard hook denies the pipe outright, and an agent
that hits that wall mid-release may skip the check instead of working around it. If the
combined log comes back truncated, re-run it one job at a time with
`gh run view --job <jobId> --repo Kangentic/kangentic --log`, taking the job ids from the
plain `gh run view <runId>`. A hit is a flake, and the project's standing
never-leave-a-flake rule makes an unresolved one a blocker: fix it, rewrite it
deterministically, or remove it with a justification before releasing. Do not ask whether to
release around it.
Report the current version (from package.json), the bump type, and what the new version will be before proceeding.
## Step 1 -- Validate
Spawn Step 1.5's `doc-auditor` agent before starting these, so the audit runs alongside them. Read
Step 1.5 now for why, and for what may and may not be applied while Step 1 is still running.
Pre-flight step 8 has already confirmed the authoritative gate. This step is a second, local
look that exists because CI's sharding and this machine's load schedule different races: the
v0.41.0 run surfaced a real latent flake here that all 11 green UI shards had never hit.
Run these checks sequentially.
1. Run `npm run typecheck`. If it fails, report type errors and stop.
2. Run `npx playwright test --project=ui`.
**Triaging a Step 1.2 failure.** What a failure means depends on its shape, and the two shapes
want opposite responses. Sort it before reacting:
- **An assertion failure is yours.** A spec that fails an `expect` is a test defect or a real
regression, and either way it blocks. Diagnose it, fix it, and re-run. The project's
never-leave-a-flake rule applies in full: fix, rewrite deterministically, or remove with a
justification. Never ask whether to release around it.
- **A worker process abort is the machine's.** `worker process exited unexpectedly (code=...)`
carries no assertion, no selector, and no wait to harden, so there is no de-flake edit that
addresses it. Chromium died. On Windows, `3221226505` (`0xC0000409`) under three workers and
1500-plus tests is resource exhaustion, not a product defect.
A process abort does NOT block when all three hold: the spec is untouched in this release's
commit range, it passes on a scoped re-run, and pre-flight step 8's CI run is green on this
commit with no `flaky` hits. Record it in the Step 7 report as an environmental failure and
keep going. If any of the three does not hold, treat it as an assertion failure and stop.
Re-running the full suite a third time to chase a clean local exit is not diligence; it is a
ten-minute coin flip that cannot tell you anything the three checks above have not already
answered.
## Step 1.5 -- Documentation Audit
Full anchor point verification before release. The audit is read-only; this step always applies
what it finds. There is no skip and no confirmation prompt here.
Spawn the agent when Step 1 STARTS, not when it finishes. The audit reads source and docs, so it
does not depend on the test results, and it runs about as long as the UI suite does. Waiting for
Step 1 first adds roughly ten minutes of wall clock to every release for nothing.
Spawn early, apply late. Hold the findings until Step 1 has PASSED, then work through the numbered
steps below. Applying them while Step 1 is still running means a Step 1 failure stops the release
with doc edits already written into main, which the next attempt's clean-tree check then trips on
for a reason that has nothing to do with the real blocker. If Step 1 fails, discard the findings
unapplied.
1. Spawn a `doc-auditor` agent with scope "all" (verify every anchor).
2. **Apply every gap it reports - unconditionally.** For each gap: add missing items, remove
extras, fix stale references. Do not ask the user; do not offer a skip.
A gap is a missing, extra, or stale item in something the docs enumerate. An observation the
auditor itself labels as prose completeness rather than an anchor gap is not one, so record it
in the Step 7 report as a follow-up instead. The release commit absorbs a large doc pass
happily, but it is not the place to start writing sections the auditor never claimed were
missing.
3. **Document every undocumented `feat:` commit** since the previous tag: scan for features not
covered in `docs/` and write the missing coverage. Unconditional - do not ask.
Points 2 and 3 are separate mandates and the auditor's prose-versus-anchor label governs point
2 only. A feature can be fully enumerated somewhere in `docs/` (so point 2 correctly reports no
gap) and still be undocumented where a user would look, which is point 3's job. v0.41.0's PR
merge-readiness pill was exactly this: `pr-integration.md` enumerated all six readiness values
and both settings that drive them, while `user-guide.md` never said the pill exists or where it
renders, so a user could read the setting that flips a PR to `ready` and not know what changed
on screen. Point 3 covers that; point 2 does not.
Keep the write proportional. Point 3 asks for the missing coverage of a shipped feature, not a
docs pass: name the thing where a user would look for it and link to the page that already
enumerates the detail.
4. **Check `@kangentic/protocol` changelog parity.** Desktop releases are frequent and protocol
releases are not, so this is the check most likely to catch a protocol release that bypassed
`/release-protocol`. It is a read-and-fix check, not a protocol release: never bump, tag, or
publish the protocol package from here.
- List the tags: `git tag --list "protocol-v*"`.
- Grep the entry headers: `Grep` for `^## \[protocol-v` in `packages/protocol/CHANGELOG.md`.
- Every tag must have a matching entry. Backfill any that do not, reconstructing each from
`git log <previousTag>..<thatTag> --oneline --no-decorate -- packages/protocol/src` and the
commit messages, and stage `packages/protocol/CHANGELOG.md` with the other doc files.
- A version bumped but never tagged is NOT a gap. See
`.claude/rules/protocol-release-parity.md` for why the tag is the line.
5. Stage the changed doc files (e.g. `git add docs/foo.md docs/bar.md`). They ride into the
release commit in Step 4.
6. Report what was fixed: list the changed doc files. A large doc pass folded into the
version-bump commit is expected and desired - do not treat it as scope creep.
## Step 2 -- Version Bump
**Skip this step entirely if this is a first release** (no previous tags, so Step 0's first-release check elected to tag the current version as-is).
Run: `npm version <patch|minor|major> --no-git-tag-version`
This updates both `package.json` and `package-lock.json` without creating a git commit or tag (we do that manually in later steps).
Also bump the launcher package to the same version:
Run: `npm version <new-version> --no-git-tag-version -w packages/launcher`
(Use the exact new version number, e.g., `npm version 0.2.0 --no-git-tag-version -w packages/launcher`)
Read the new version from `package.json` and `packages/launcher/package.json` to confirm both match.
**Do not bump `packages/protocol`.** `@kangentic/protocol`'s version is deliberately decoupled
from Kangentic's own -- it ships on its own cadence via the separate `/release-protocol` skill
and `publish-protocol.yml` workflow, not this one. See that skill for details.
## Step 3 -- Generate Changelog
1. **Find the previous tag:** Run `git describe --tags --abbrev=0 --match "v*"` (the filter matters
for the reason Step 0 point 1 gives: without it this returns a `protocol-v*` tag and the
changelog silently covers the wrong range). If no tags exist, use the root commit as the
starting point (this is the first release).
2. **Collect commits:** Run `git log <previousTag>..HEAD --oneline --no-decorate` (or `git log --oneline --no-decorate` if no previous tag).
3. **Group commits** into categories using conventional commit prefixes:
- **Breaking Changes** -- commits with `!` after the type (e.g., `feat!:`, `fix!:`) or containing `BREAKING CHANGE` in the subject
- **Features** -- commits with `feat:` prefix
- **Fixes** -- commits with `fix:` prefix
- **Other** -- commits with `chore:`, `docs:`, `refactor:`, `test:`, `style:`, `perf:`, `ci:`, `build:` prefix
- **Fallback** -- commits without a conventional prefix get loose keyword matching for backwards compatibility:
- Starting with "Add", "Implement", "Create", or containing "feature" -- Features
- Starting with "Fix" or containing "bug", "resolve" -- Fixes
- Everything else -- Other
- When displaying commit messages in the changelog, strip the conventional prefix (e.g., `feat: add dark mode` becomes `Add dark mode`)
4. **Format the changelog entry:**
```markdown
## [vX.Y.Z] - YYYY-MM-DD
### Breaking Changes
- Commit message here (abc1234)
### Features
- Commit message here (abc1234)
### Fixes
- Commit message here (def5678)
### Other
- Commit message here (ghi9012)
```
Omit any category section that has no entries.
5. **Read `CHANGELOG.md`**, then use the **Edit tool** to insert the new entry after the `<!-- releases -->` marker line. If the file doesn't exist or doesn't have the marker, stop with an error.
## Step 3.5 -- Generate Release Notes
Generate a concise, user-friendly summary for the GitHub Release draft body. This is separate from the CHANGELOG -- the CHANGELOG is the full technical log, while release notes are a brief summary for end users.
1. **Use the same commit list from Step 3**, but rewrite them in plain language:
- Strip conventional commit prefixes (`feat:`, `fix:`, etc.)
- Remove commit hashes
- Rewrite terse commit subjects into clear, user-friendly descriptions
- Merge related commits into single bullet points where appropriate (e.g., three commits that all improve the same feature become one bullet)
2. **Group into sections:**
- **What's New** -- features and enhancements
- **Bug Fixes** -- fixes
- **Breaking Changes** -- only if applicable
- Omit any section that has no entries. Do not include an "Other" section -- skip chores, docs, refactors, CI, and build commits.
3. **Write the release notes** to `RELEASE_NOTES.md` at the repo root using the Write tool:
```markdown
## What's New
- Dark mode support
- Desktop notifications for background tasks
## Bug Fixes
- Fixed crash when opening an empty board
```
4. This file is committed in Step 4 and used by CI to populate the draft GitHub Release body automatically.
## Step 4 -- Commit
1. Stage the changed files: `git add package.json package-lock.json packages/launcher/package.json CHANGELOG.md RELEASE_NOTES.md`
(If this is a first release with no version bump, only stage `CHANGELOG.md RELEASE_NOTES.md`)
2. Write the commit message using the **Write tool** to `.kangentic/COMMIT_MSG.tmp` (a
main-checkout path, and the file normally already holds a previous release's message, so read
it first if the tool refuses to overwrite an unread file):
```
chore(release): vX.Y.Z
```
Add the session's attribution lines below the subject if the harness asks for them.
3. Commit: `git commit -F .kangentic/COMMIT_MSG.tmp`
## Step 5 -- Tag
Run: `git tag -a vX.Y.Z -m "Release vX.Y.Z"`
## Step 6 -- Push
Run these sequentially:
1. `git push origin main` -- push the release commit
2. `git push origin vX.Y.Z` -- push the tag (triggers `release.yml` workflow)
**If either push fails**, report the error and stop. Do not force-push.
## Step 7 -- Report
Summarize the release:
- Version: vX.Y.Z
- Tag: vX.Y.Z
- Commits included: N
- Changelog entry: show the generated entry
- **Release notes:** Read `RELEASE_NOTES.md` and display the contents. Tell the user: "These release notes will be applied to the draft GitHub Release automatically by CI."
- GitHub Actions: link to `https://github.com/Kangentic/kangentic/actions`. The tag push triggers the Release workflow, which creates ONE draft Release, builds all three platforms into it, verifies the asset manifest, and then publishes it automatically.
**Watch the run before opening anything.** Get the run id with `gh run list --repo
Kangentic/kangentic --workflow=release.yml --limit 1`, then wait on it with `gh run watch <runId>
--repo Kangentic/kangentic --exit-status` (run it in the background; it takes 15 to 25 minutes,
the last ten of them the `demo-posters` job shooting the docs poster set after the release is
already published).
**Then** verify the end state rather than trusting the exit code, and only after that open the
releases page in the user's browser with `start
https://github.com/Kangentic/kangentic/releases`:
- `gh api repos/Kangentic/kangentic/releases/tags/vX.Y.Z --jq '{draft, asset_count: (.assets | length)}'` must report `draft: false` and 12 assets: the 11 build assets `scripts/release-assets.js` lists, plus `demo-posters-X.Y.Z.zip` from the `demo-posters` job, which runs after the release is published. 11 after a green `gh run watch` means that job did not attach the poster set; read its log.
- `npm view kangentic version` must report the new version.
Opening the releases page while the builds are still running is what caused the v0.39.0 failure
below, so the order here is the guard, not a preference: it puts a draft and a Publish button in
front of a human for the ten minutes when clicking it does the most damage.
**Never publish the draft by hand.** Publishing is automatic once
`scripts/verify-release-assets.js` confirms the tag resolves to exactly one release carrying all
11 expected build assets. So a release still sitting as a draft after the workflow finishes means that
gate FAILED, and the draft is presumed incomplete. Clicking Publish in the GitHub UI bypasses the
only check that stands between a partial release and every user's auto-updater, which is exactly
how v0.35.0 shipped macOS-less. Read the `publish-release` job log, fix the cause, and re-run the
workflow instead.
Publishing it EARLY, while the builds are still running, is the worse half and is how v0.39.0
first shipped empty. electron-builder uploads only into a draft: handed a published release it
skips every artifact with `existing type not compatible with publishing type` and the builds
still exit 0, leaving a published release carrying nothing. `create-draft-release` now fails the
run in seconds when it finds that state, but the recovery is still manual: `gh release delete
vX.Y.Z --yes` (the tag survives), then a FULL re-run with `gh run rerun <runId>`. Not
`--failed`, which re-runs only the failed job and leaves the other platforms' assets unbuilt.
## Step 8 -- Mark the Sentry issues this release fixes
Runs only after Step 7's end-state verification. The Sentry release `Kangentic@X.Y.Z` is created by
the bundler plugin during the CI build, so it does not exist before then and nothing here can be
done earlier.
The marker must name the release that CARRIES the fix. Naming the release an issue was last seen on
reopens it on exactly the builds that legitimately lack the fix. Read
`.claude/skills/sentry/SKILL.md` and follow its "Auth" and "Resolution markers" sections for the
token, the request bodies, and the traps; do not re-derive them here.
1. **Derive the candidates:** Run
`git log <previousTag>..vX.Y.Z --grep="DESKTOP-" --format=%H%n%B`, where `<previousTag>` is the
value Step 0 captured and `vX.Y.Z` is the tag Step 5 created. Do NOT re-derive `<previousTag>`
here. Step 5 has already tagged this release, so a fresh `git describe --tags --abbrev=0
--match "v*"` now returns the NEW tag and the range comes back empty. That is the one silent
failure this step has, and an empty range reads exactly like a clean run. Collect every shortId
the commit bodies name.
2. **Sort the candidates mechanically, then act without asking.** The scan produces candidates, not
answers, because a commit body cites shortIds it does not fix. Read each candidate's issue
payload first, both its `status` and its newest `set_resolved_in_release` activity entry: a
commit body on its own cannot tell you whether an issue is already resolved somewhere else. Then
sort into exactly three buckets and do not deliberate past this:
- **Currently `unresolved`, and a commit in this range states that it fixes the issue.** Mark it.
- **Currently `unresolved`, but the commit names it only as context or prior art.** Skip it and
say so in the report. Getting this one wrong is self-correcting, because a later event on a
newer release reopens the issue.
- **Already `resolved`, whatever the commit says.** Never write to it. Report its current
marker, and say so explicitly when that marker names a release that predates the fix, because
that is the one case a human has to act on and nothing else in this step surfaces it.
That third rule is what replaces the old confirmation prompt, and it is strictly safer than the
prompt was. The failure this step exists to prevent is re-marking an already-resolved issue to
the version being shipped, which reopens it on exactly the builds that legitimately carry the
fix: `b653463d` names DESKTOP-C, whose fix shipped in v0.39.0. Every instance of that bug is a
write to an issue that was already resolved, so refusing those writes removes the bug outright
rather than asking a human to catch it.
The cost is that a wrong marker written by the GitHub integration, which resolves an issue
without being asked when a merged PR names a shortId, gets reported rather than corrected.
Correcting one takes two ordered writes rather than one, per the sentry skill's "Four things
bite" item 1. That makes it a deliberate act and the one part of this step genuinely worth a
human, so it does not belong on the unattended path.
3. **Mark each issue in the first bucket** against `Kangentic@<the version just shipped>`.
4. **Verify, then say which way it went.** Read each issue's newest `set_resolved_in_release`
activity entry back, because `statusDetails` alone cannot confirm a write landed. Then report
the outcome in one line, including the empty ones: "no Sentry shortIds in this range", or "403
on the resolve PUT, these issues are unmarked: ...". Per
`.claude/rules/release-gates-fail-loudly.md`, a step that guarantees something says which way
it went.
**This step never fails the release.** It runs after the release is already published, so its only
failure mode is a report. Do not stop the flow, do not retry in a loop, and do not roll anything
back.
## Allowed Tools
Use `Read`, `Glob`, `Grep`, `Bash` (for `git`, `gh`, `npm`, `npx`, and `curl` commands), `Write` (for commit message temp file), and `Edit` (for CHANGELOG.md).
`gh` carries the entire GitHub half of the release: the symbol-secret check and the leftover-release
check in pre-flight, and every Step 7 call that watches the run and verifies what was published.
Without `Bash(gh:*)` the release can still tag and push, which is the worst possible failure shape,
because it ships the build and then cannot confirm what it shipped.
Step 8 needs two more: `PowerShell`, for the sentry skill's Windows request pattern, which chains
with `;` and so cannot go through `Bash` under `.claude/rules/bash-single-command.md`; and
`Bash(curl:*)` for its macOS and Linux form. Without those grants Step 8 cannot make a single
Sentry request. It reaches the sentry skill's instructions with `Read`, which is already granted,
rather than by invoking the skill.
**CRITICAL: No chained commands.** Every Bash call must contain exactly ONE command. Never use `&&`, `||`, `|`, or `;`. Use `git -C <path>` for git commands in another directory -- never `cd <path> && git ...`.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!