agentleFS
maxwell2732maxwell2732/claudecode-research-harness-workflow/skills/harness-release/SKILL.md

harness-release

Skill42 starsChanged 6 days agoMITView on GitHub

Generic release automation for projects using Keep a Changelog + GitHub. Single confirmation gate then end-to-end automation: bump detection, CHANGELOG promotion, PR/main merge, tag, GitHub Release. Trigger: release, version bump, publish. Do NOT load for: implementation, review, planning, setup.

  • Commits and pushes

1 other project uses a version of this, 1 of them edited.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

32 rules. About 0 are common, 15 are distinctive, and 17 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈15

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 17

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Always output the following literal marker in the first response on bare invocation:

    line 38
  2. Distinctive:

    Do not stop with "No tasks" or "Waiting for instructions."

    line 56
  3. Very specific:

    Check git status --porcelain and git log @{upstream}..HEAD / main..HEAD to identify the target "work done so far"

    line 60
  4. Very specific:

    Check .claude/state/review-result.json and .claude/state/review-approved.json to verify whether the target work has an APPROVEd review

    line 61
  5. Very specific:

    When no APPROVEd review exists, confirm with AskUserQuestion

    line 62
  6. Very specific:

    When user selects "Start from review," launch harness-review and do not proceed to release until APPROVE is returned

    line 63
  7. Very specific:

    When harness-review returns REQUEST_CHANGES, hold the release, fix with harness-work, then re-run harness-review.

    line 64
  8. Very specific:

    After harness-review returns APPROVE, create a work commit for the working tree

    line 65
  9. Distinctive:

    After the working tree is clean, proceed to normal release preflight / confirmation gate / PR merge / tag / GitHub Release

    line 66
  10. Very specific:

    When running harness-release and review approval cannot be confirmed, do not release by guessing.

    line 70
  11. Very specific:

    When user selects "Start from review," start harness-review in the same session.

    line 84
  12. Very specific:

    If review is APPROVE, return to the harness-release Work Commit Gate.

    line 86
  13. Very specific:

    If review is REQUEST_CHANGES, hold release, fix with harness-work, then re-run harness-review.

    line 87
  14. Distinctive:

    Only return to the user in these cases:

    line 90
  15. Very specific:

    Do not use REQUEST_CHANGES alone as a final stop reason.

    line 96
  16. Very specific:

    When unclear, present 2-3 commit message candidates with AskUserQuestion.

    line 110
  17. Very specific:

    After creating the work commit, verify or update commit_hash in .claude/state/review-result.json, then proceed to release preflight.

    line 111
  18. Distinctive:

    After entering normal release preflight, treat dirty working tree as failure as before.

    line 113
  19. Distinctive:

    Do not proceed to version bump / tag / GitHub Release with a dirty tree.

    line 114
  20. Distinctive:

    When these are not met, Preflight detects and aborts.

    line 141
  21. Distinctive:

    When wanting to commit "work done so far" on bare release, complete the Review Gate and Work Commit Gate before this check.

    line 219
  22. Distinctive:

    Do not abort and finish just because of an unreviewed dirty tree at this check.

    line 220
  23. Distinctive:

    Search in this priority order.

    line 226
  24. Very specific:

    Use --json for machine processing or CI:

    line 285
  25. Very specific:

    When user explicitly specifies /release patch|minor|major, that takes priority.

    line 310
  26. Very specific:

    Create the form with ## [<new>] - YYYY-MM-DD inserted between ## [Unreleased] and ## [<previous>]

    line 318
  27. Distinctive:

    When all drafts are ready, present to user once:

    line 348
  28. Distinctive:

    After the release commit in Post-Gate, merge the GitHub PR to the default branch before creating the tag.

    line 398
  29. Distinctive:

    When an existing PR exists, update its body and merge without creating a new one.

    line 414
  30. Distinctive:

    Create the tag after this Gate is complete, against HEAD of the default branch or a commit reachable from the release commit.

    line 416
  31. Distinctive:

    Run all Pre-Gate steps and display content through Confirmation Gate, but stop at the gate without proceeding to Post-Gate.

    line 434
  32. Very specific:

    Project-independent: No assumptions about VERSION file format, mirrors, residue checks etc.

    line 480

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Harness Release (Generic)

Generic release automation skill for any project using Keep a Changelog + GitHub.

Design principle: Single confirmation gate. The user sees and approves the full plan once. After approval, execution continues uninterrupted: file rewrite → commit → branch push → PR create/update → merge to default branch → tag on default branch → GitHub Release.

Definition of release complete: A release is not complete just by "creating a tag and GitHub Release." Complete means: the target work and release bump are merged into the default branch (usually main), the release tag points to a commit reachable from the default branch, and the GitHub Release is published against that tag.

Literal invocation note: The entry point for this skill uses literal commands as-is: harness-release, /release, /release patch, /release --dry-run.

Bare invocation contract

if $ARGUMENTS == "": → Interpret as "commit work done so far and complete through PR/main merge before releasing," and run Review Gate detection → Auto-proceed to Step 0 (Review Gate) only when exactly one target work can be determined → When target is unclear or no review state exists, present options with AskUserQuestion before proceeding

Always output the following literal marker in the first response on bare invocation:

RELEASE_AUTOSTART: target=<work-summary>, base_ref=<ref>, mode=<patch|minor|major|auto>

Prohibited responses: "Task is unclear," "Waiting for instructions," "No tasks," "Waiting for additional instructions."

<!-- Above block is AUTO-START CONTRACT. Compliant with skill-editing.md "within first 3 lines" rule. patterns.md P27 solution triple (machine-readable condition + prohibited action literal + AUTOSTART marker) -->

Output Contract (P35: "appears stuck" UX countermeasure)

The last line of skill output must always include this literal:

↑Claude will summarize this result. Press Enter to continue, or enter a new prompt for a different instruction.

This is an explicit instruction for the UX problem where text displayed via <local-command-stdout> makes users feel it has "stopped" (patterns.md P35).

harness-release / /release alone means "commit work done so far and release through PR/main merge." The older phrasing "commit work done so far and release" has the same intent, but the completion condition must always include PR/main merge. Do not stop with "No tasks" or "Waiting for instructions."

On bare release, run the Review Gate and Work Commit Gate before the normal release preflight.

  1. Check git status --porcelain and git log @{upstream}..HEAD / main..HEAD to identify the target "work done so far"
  2. Check .claude/state/review-result.json and .claude/state/review-approved.json to verify whether the target work has an APPROVEd review
  3. When no APPROVEd review exists, confirm with AskUserQuestion
  4. When user selects "Start from review," launch harness-review and do not proceed to release until APPROVE is returned
  5. When harness-review returns REQUEST_CHANGES, hold the release, fix with harness-work, then re-run harness-review. Loop until APPROVE.
  6. After harness-review returns APPROVE, create a work commit for the working tree
  7. After the working tree is clean, proceed to normal release preflight / confirmation gate / PR merge / tag / GitHub Release

Review Gate AskUserQuestion

When running harness-release and review approval cannot be confirmed, do not release by guessing. Present the following Ask:

question: "harness-release will commit work done so far and release, but no APPROVE review was found for this work. How would you like to proceed?"
options:
  - label: "Start from review (Recommended)"
    description: "Run harness-review and proceed to commit/release only if APPROVE is returned."
  - label: "release dry-run"
    description: "No file changes; check only the release plan and missing gates."
  - label: "Cancel"
    description: "Stop without review or release."

When user selects "Start from review," start harness-review in the same session. Target determination in harness-review follows harness-review's own bare review contract. If review is APPROVE, return to the harness-release Work Commit Gate. If review is REQUEST_CHANGES, hold release, fix with harness-work, then re-run harness-review. This post-fix re-review loop continues until APPROVE.

Only return to the user in these cases:

  1. Fix requires decisions about spec source of truth / Plans.md / API / permission / migration / billing etc., and AskUserQuestion is needed
  2. Multiple fix approaches exist where choice affects user value or compatibility
  3. User selected release dry-run or Cancel in Ask

Do not use REQUEST_CHANGES alone as a final stop reason.

Work Commit Gate

On bare release, when there are uncommitted changes in the working tree, create a reviewed work commit separately from the release version bump commit first.

git status --short
git diff --stat
git add <reviewed files>
git commit -m "<type>: <summary>"

Generate commit message briefly from review summary / Plans.md task / branch name. When unclear, present 2-3 commit message candidates with AskUserQuestion. After creating the work commit, verify or update commit_hash in .claude/state/review-result.json, then proceed to release preflight.

After entering normal release preflight, treat dirty working tree as failure as before. Do not proceed to version bump / tag / GitHub Release with a dirty tree.

Quick Reference

/release              # Review gate → commit → PR/main merge → release work done so far
/release patch        # Explicitly specify bump as patch
/release minor        # Explicitly specify bump as minor
/release major        # Explicitly specify bump as major
/release --dry-run    # Display plan only, do not execute

Prerequisites

Projects where this skill operates must satisfy:

  1. CHANGELOG.md in Keep a Changelog format
  2. [Unreleased] section exists
  3. Has one of the following version files:
    • VERSION (standalone file)
    • package.json (npm)
    • pyproject.toml (Python, [project] or [tool.poetry])
    • Cargo.toml (Rust, [package])
  4. gh CLI installed and authenticated
  5. Git remote origin points to GitHub
  6. For Claude Code plugin projects: claude CLI supports plugin tag

When these are not met, Preflight detects and aborts.

prUrlTemplate multi-host review URL is recognized as a future candidate, but this skill's release automation still uses gh CLI and GitHub remote as the primary path. Auto-retrieval of owner / branch / release asset / CI metadata has large host-by-host differences, so Phase 56.2.3 remains docs-only.

Single gate flow

[Bare release only: work review/commit pre-step]
  ↓
  0. Review Gate (AskUserQuestion → harness-review if no review)
  0.5 Work Commit Gate (commit review-APPROVEd work separately from release bump)
  ↓
[Pre-Gate: information collection only, no file changes]
  ↓
  1. Preflight (working tree clean / CHANGELOG / gh etc. checks)
  2. Version file auto-detection
  3. Read current version
  4. Claude plugin tag preflight (plugin projects only)
  5. Parse [Unreleased] content → estimate bump level
  6. Calculate new version
  7. Create CHANGELOG diff draft (in memory)
  8. Create GitHub Release notes draft (in memory)

★━━━━━━ Single Confirmation Gate ━━━━━━★
  Present full plan to user once:
    - Detected version file
    - Current version → new version
    - Bump determination reason (e.g., "minor because ### Added is in [Unreleased]")
    - CHANGELOG change preview
    - GitHub Release notes draft
    - Files to commit
    - Final actions (branch push + PR merge + tag + release publish)

  User response:
    "yes"              → Proceed to Post-Gate
    "<fix instruction>" → Regenerate draft per instruction, re-confirm
    "cancel/no"        → Do nothing and exit
★━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━★
  ↓
[Post-Gate: after approval, no interruption]

  9. Rewrite version file
  10. Rewrite CHANGELOG.md ([Unreleased] → [X.Y.Z] promotion + compare link)
  11. git add + commit
  12. Release branch push
  13. PR create/update
  14. Merge to default branch
  15. Fetch/checkout default branch; verify release commit is reachable
  16. Claude plugin tag validation + tag (plugin projects only)
  17. GitHub Release semver tag (only for projects that need it)
  18. git push origin <default-branch> --tags
  19. gh release create vX.Y.Z
  20. Completion report

Pre-Gate details

1. Preflight

# Required tools
command -v gh >/dev/null || { echo "gh CLI not found"; exit 1; }
command -v python3 >/dev/null || { echo "python3 required"; exit 1; }

# Working tree
if [ -n "$(git status --porcelain)" ]; then
  echo "Uncommitted changes in working tree"; exit 1;
fi

# CHANGELOG
[ -f CHANGELOG.md ] || { echo "CHANGELOG.md not found"; exit 1; }
grep -q "^## \[Unreleased\]" CHANGELOG.md || { echo "[Unreleased] section not found"; exit 1; }

# plugin/mirror projects
scripts/release-preflight.sh

This working tree clean check is the gate for normal release preflight. When wanting to commit "work done so far" on bare release, complete the Review Gate and Work Commit Gate before this check. Do not abort and finish just because of an unreviewed dirty tree at this check.

scripts/release-preflight.sh also detects mirror drift in opencode/, skills-codex/, codex/.codex/skills/ before tag creation. When node scripts/build-opencode.js generates a diff, stop the release, commit that diff, then proceed to tag.

2. Version file auto-detection

Search in this priority order; use the first found as authoritative:

import os, json, re
import tomllib  # Python 3.11+

def detect_version_file():
    if os.path.exists("VERSION"):
        with open("VERSION") as f:
            return ("VERSION", f.read().strip(), None)
    if os.path.exists("package.json"):
        with open("package.json") as f:
            data = json.load(f)
        return ("package.json", data["version"], None)
    if os.path.exists("pyproject.toml"):
        with open("pyproject.toml", "rb") as f:
            data = tomllib.load(f)
        if "project" in data:
            return ("pyproject.toml", data["project"]["version"], "[project]")
        if "tool" in data and "poetry" in data["tool"]:
            return ("pyproject.toml", data["tool"]["poetry"]["version"], "[tool.poetry]")
    if os.path.exists("Cargo.toml"):
        with open("Cargo.toml", "rb") as f:
            data = tomllib.load(f)
        return ("Cargo.toml", data["package"]["version"], "[package]")
    raise RuntimeError("No supported version file found")

Details: version-files.md

3. Claude Plugin Tag Preflight

For projects with .claude-plugin/plugin.json, create a Claude plugin release tag separately from the normal GitHub Release tag.

In short: before assembling git tag -a manually, pass Claude Code's own plugin validation and then create the {plugin-name}--v{version} tag.

Pre-Gate does not rewrite files; verify the following. Read version sync with structured parser, not grep/sed:

command -v claude >/dev/null || { echo "claude CLI not found"; exit 1; }
claude plugin validate .claude-plugin/plugin.json

HARNESS_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-.}"
python3 "${HARNESS_PLUGIN_ROOT}/scripts/check-release-version-sync.py" --root .

claude plugin tag .claude-plugin --dry-run

${HARNESS_PLUGIN_ROOT}/scripts/check-release-version-sync.py reads all existing release surfaces and determines canonical in the order VERSION > package.json > .claude-plugin/plugin.json > .codex-plugin/plugin.json. Then it does not proceed to tag/release if any of the following have mismatches or are missing:

  • VERSION
  • package.json .version
  • .claude-plugin/plugin.json .version
  • .codex-plugin/plugin.json .version
  • .claude-plugin/marketplace.json .metadata.version
  • .claude-plugin/marketplace.json .plugins[].version (each plugin entry in the array)

On mismatch, show which surface differs from canonical or which field is missing/invalid. Use --json for machine processing or CI:

python3 "${HARNESS_PLUGIN_ROOT}/scripts/check-release-version-sync.py" --root . --json

This check prevents 3 accidents:

  • Cutting a tag with VERSION and .claude-plugin/plugin.json version out of sync
  • Proceeding to release workflow with stale package.json / marketplace entry version
  • Getting stuck on plugin install/update side after skipping plugin manifest/marketplace entry validation

--dry-run shows the tag name claude plugin tag would create and the internal git tag -a / push equivalent command. Include the command shown here in the Confirmation Gate plan.

4. Bump auto-estimation

Parse headings directly under [Unreleased] to determine bump level:

Headings in [Unreleased]Estimated bump
Contains ### Breaking Changes or ### Removedmajor
Contains ### Added (no Removed/Breaking)minor
Only ### Fixed / ### Changed / ### Securitypatch
Empty sectionerror: no release target

When user explicitly specifies /release patch|minor|major, that takes priority. Details: bump-detection.md

5. CHANGELOG draft creation (in memory)

Calculate the following; do not write yet:

  1. Cut out body of ## [Unreleased]
  2. Create the form with ## [<new>] - YYYY-MM-DD inserted between ## [Unreleased] and ## [<previous>]
  3. Trailing compare link:
    • [Unreleased]: .../compare/v<prev>...HEAD → v<new>...HEAD
    • Add [<new>]: .../compare/v<prev>...v<new>
  4. Dynamically extract repo URL from existing [Unreleased]: line

6. Release notes draft creation (in memory)

Generate GitHub Release markdown from the ## [<new>] section content:

## What's Changed

**<release theme (1 line)>**

### Before / After
<table>

### Added / Changed / Fixed / Removed
<copy applicable sections>

---

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Details: release-notes.md

Confirmation Gate

When all drafts are ready, present to user once:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Release Plan: v<old> → v<new> (<bump>)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Version file: <detected file>
 Bump reason:  <why this level was chosen>

 CHANGELOG changes:
   <N> changes detected in [Unreleased]
   Confirmed as [<new>] - YYYY-MM-DD
   Compare link added

 GitHub Release notes preview:
   <first 10 lines>
   ...

 Files to modify:
   - <version file>
   - CHANGELOG.md

 Final actions:
   - git commit -m "chore: release v<new>"
   - git push origin <release-branch>
   - gh pr create/update + gh pr merge into <default-branch>
   - git fetch origin <default-branch> && git checkout <default-branch>
   - claude plugin tag .claude-plugin --push --remote origin  # plugin projects only; run on default branch
   - git tag -a v<new>                                        # GitHub Release semver tag if needed; create on default branch
   - git push origin <default-branch> --tags
   - gh release create v<new>

Proceed? [yes / cancel / <fix instruction>]

Post-Gate details

Execute without interruption after approval. On failure, follow this policy:

Failure pointRecovery
File rewrite failureAbort there; local is left dirty for human judgment
Commit failureHook rejection etc. Present cause to user and prompt for fix
PR create/merge failureStop as incomplete release; do not proceed to tag/GitHub Release
Plugin tag validation failureFix VERSION / .claude-plugin/plugin.json / marketplace entry mismatch; do not proceed to tag creation
Push failureRemote-side issue. Local commit/tag remains.
gh release create failureTag is already pushed; existing release.yml safety net may fire, or run gh release create manually

PR / Main Merge Gate

After the release commit in Post-Gate, merge the GitHub PR to the default branch before creating the tag.

release_branch="$(git branch --show-current)"
default_branch="${HARNESS_RELEASE_DEFAULT_BRANCH:-main}"

git push -u origin "$release_branch"
gh pr create --base "$default_branch" --head "$release_branch" --title "chore: release v<new>" --body "<release summary>"
gh pr merge --merge --delete-branch=false

git fetch origin "$default_branch" --tags
git checkout "$default_branch"
git pull --ff-only origin "$default_branch"
git merge-base --is-ancestor "<release-commit>" "origin/$default_branch"

When an existing PR exists, update its body and merge without creating a new one. When repository policy requires squash merge, verify that release bump content (version files + CHANGELOG + source commits) is included in the default branch — not just the release commit hash.

Create the tag after this Gate is complete, against HEAD of the default branch or a commit reachable from the release commit. Do not create a GitHub Release with a tag pointing to a commit that exists only on the release branch.

Claude plugin project tag creation

For projects with .claude-plugin/plugin.json, verify version sync on the default branch once more after PR/main merge, then create the plugin tag:

HARNESS_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-.}"
python3 "${HARNESS_PLUGIN_ROOT}/scripts/check-release-version-sync.py" --root .

claude plugin tag .claude-plugin --dry-run
claude plugin tag .claude-plugin --push --remote origin

The tag claude plugin tag creates is in {plugin-name}--v{version} format. For projects where existing GitHub Release workflows assume a vX.Y.Z tag, also create git tag -a v<new> separately from the plugin tag. Let claude plugin tag handle distribution tags; treat the GitHub Release semver tag as a compatible release automation surface.

--dry-run mode

Run all Pre-Gate steps and display content through Confirmation Gate, but stop at the gate without proceeding to Post-Gate.

For Claude plugin projects, even in dry-run, execute python3 "${HARNESS_PLUGIN_ROOT}/scripts/check-release-version-sync.py" --root . and claude plugin tag .claude-plugin --dry-run to show the actual plugin tag name to be created and push targets. If any version surfaces in VERSION / package.json / .claude-plugin/plugin.json / .codex-plugin/plugin.json / .claude-plugin/marketplace.json are mismatched or missing, stop at dry-run time.

Environment variables

For per-project adjustments:

VariableDescription
HARNESS_RELEASE_PROJECT_ROOTRepository root (default: $(pwd))
HARNESS_RELEASE_BRANCHBranch to push (default: current branch)
HARNESS_RELEASE_DEFAULT_BRANCHDefault branch for PR merge target (default: main)
HARNESS_RELEASE_HEALTHCHECK_CMDAdditional command to run in Preflight
HARNESS_RELEASE_SKIP_GH1 skips GitHub Release creation

CHANGELOG writing rules

The [Unreleased] section must always have one of these subsections:

## [Unreleased]

### Added       ← minor
### Changed     ← patch
### Deprecated  ← minor
### Removed     ← major
### Fixed       ← patch
### Security    ← patch
### Breaking Changes  ← major (non-standard in KaCL but common)

This skill parses these headings mechanically, so heading variations (### Fix / ### Bug Fixes etc.) cannot be recognized. Use standard KaCL headings.

Related skills

  • harness-release-internal - Harness-specific preflight/finalization run additionally for claude-code-harness itself (not distributed)
  • harness-plan - Plans.md management
  • harness-review - Code review before release

Design philosophy

  • Single gate: User makes one judgment per release. Multiple mini-confirmations become rubber-stamps and lose meaning.
  • Draft everything upfront: No "reconsideration" after entering Post-Gate. Assemble all drafts before the gate.
  • Main merge is the completion condition: Release tag / GitHub Release only created after default branch merge. Branch-only releases are treated as incomplete.
  • Failures are transparent: On mid-execution failure, do not attempt auto-rollback; present the current state to the user for judgment.
  • Project-independent: No assumptions about VERSION file format, mirrors, residue checks etc. specific to certain environments. Harness-specific processing for the main harness is separated into harness-release-internal.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.