name: release description: Prepare, validate, tag, publish, and monitor guarded PwrAgent desktop releases. Use when the user asks to release PwrAgent, prepare a vX.Y.Z or vX.Y.Z-prerelease tag, update release notes or CHANGELOG.md for a desktop release, verify package.json/tag/changelog alignment, trigger the macOS signed/notarized release workflow, or inspect release workflow status.
Release
Use this skill for PwrAgent desktop releases published by the
.github/workflows/release.yml Universal macOS workflow.
Read First
Read these files before changing release metadata:
- ../../../docs/desktop-release-runbook.md
- ../../../docs/desktop-distribution-phase-2-runbook.md when the release affects update feeds or distribution repos
- ../../../.github/workflows/release.yml
- ../../../scripts/check-desktop-release-metadata.mjs
Guardrails
Release from
mainfor the active next-version train, or from a long-lived maintenance branch namedreleases/<major>.<minor>for patch releases on a prior train. Do not include the patch component in maintenance branch names: usereleases/1.0, notreleases/1.0.xorreleases/1.0.1.Start from a clean working tree. If tracked files are dirty, stop and ask before changing release metadata.
Fetch tags before planning:
git fetch origin --tagsTreat
apps/desktop/package.jsonas the desktop release version source. The rootpackage.jsonversion is not the desktop app release version.Always use a leading-
vtag such asv1.0.0-alpha.5.The tag version,
apps/desktop/package.jsonversion, andCHANGELOG.mdrelease heading must match.Before moving
mainto a new major/minor train, verify that the prior train's maintenance branch exists. For example, before preparing1.1.0-beta.1from a current1.0.*main, check fororigin/releases/1.0. If it is missing, stop and ask whether to create it from the current prior-train release commit before bumping version metadata.Patch releases for an existing train must land on that train's branch. For example, prepare
v1.0.1onreleases/1.0, not onmain.Before pushing a release tag, verify the
apple-signingGitHub Environment exists, requires reviewer approval, is scoped to release tags, and has the Apple signing/notarization secrets required by the workflow.Do not create or push the tag until the version and changelog are committed and present on the intended release branch.
Do not create the GitHub Release by hand before the build succeeds. Let electron-builder create or update the release from the signed/notarized CI build; the workflow publishes the matching changelog entry to the GitHub Release body after release assets are uploaded.
Do not use GitHub generated release notes as the final notes.
A release is not complete when the workflow reaches the
apple-signingapproval gate. After approval, continue monitoring through the release-notes publishing job, and verify the release body is non-empty.Do not force-push the default branch or rewrite an existing release tag without explicit user approval.
Keep MIT licensing intact: do not change first-party license metadata or remove license disclosures without an explicit policy change.
Release Branch Preflight
For every release, identify RELEASE_BRANCH before editing files:
- Active-train beta or stable release:
main. - Prior-train patch release:
releases/<major>.<minor>.
If the user asks to cut a new major/minor version from main, compare the
current desktop version's major/minor with the requested version's major/minor.
When they differ, check for the current train's maintenance branch:
git ls-remote --heads origin releases/<current-major>.<current-minor>
If it is missing, ask before proceeding:
We are about to move main from the <old-train> train to the <new-train> train,
but there is no releases/<old-train> maintenance branch. Guidance is to create
it before bumping main so future <old-train>.x security patches can be cut
cleanly. Create releases/<old-train> from the current <old-train> release commit
now?
Create that branch before the version-bump commit that starts the new train:
git switch main
git fetch origin main --tags
git pull --ff-only
git switch -c releases/<old-train> v<old-train>.<patch>
git push origin releases/<old-train>
git switch main
Use the exact prior-train release tag as the branch point. For the first stable
1.0 release, cut and tag v1.0.0, create releases/1.0 at v1.0.0, then bump
main to 1.1.0-beta.1.
Prepare Release Metadata
Determine the next version from the previous tag and user intent:
git tag --sort=-version:refname | head -n 10 gh release list --limit 10Update
apps/desktop/package.jsonwithout creating a tag yet:pnpm --filter @pwragent/desktop version <version> --no-git-tag-versionIf that command is not available in the current pnpm version, edit only
apps/desktop/package.jsonand preserve JSON formatting.Add a top
CHANGELOG.mdentry:## v1.0.0-alpha.5 - YYYY-MM-DDWrite user-facing bullets from merged PRs and direct commits since the last release. Preserve the same substance in GitHub release notes.
Release notes must give context first, not just describe the code delta. Start each bullet with the user-visible area or feature, then state whether it was added, improved, or fixed. Keep bullets punchy and readable by operators:
- Composer - Improved complex Markdown pastes with lists, inline code, and nested code blocks. - Thread Search - Escape now dismisses search, pairing with Cmd/Ctrl+Shift+F to open it. - Thread List Pull Request Info - Merged PR commits no longer show as unpushed work. - Minor - Dependency updates and small UI polish.Avoid release-note bullets that only say "Improved handling", "Added plumbing", "Updated dependencies", or "Fixed packaging" without naming the feature surface and why users should care. Roll maintenance-only changes into a short
Minor - ...bullet unless they materially affect installs, updates, or data safety.Run the metadata gate locally before committing:
RELEASE_TAG=v<version> pnpm release:checkRun normal repo gates unless the user explicitly narrows verification:
pnpm typecheck pnpm test
Commit, Land, And Tag
Commit the version and changelog together. Use a signed commit; this repo's git config should already sign commits with SSH.
git add apps/desktop/package.json CHANGELOG.md
git commit -m "chore(release): prepare v<version>"
Preferred fast path: if maintainer direct-push bypass is enabled for
RELEASE_BRANCH, push the signed release metadata commit directly. This avoids
running PR CI and then running the same gates again from the release tag.
git push origin HEAD:<RELEASE_BRANCH>
git fetch origin <RELEASE_BRANCH> --tags
git pull --ff-only
Fallback path: if direct push to RELEASE_BRANCH is rejected, push the release
metadata commit to a short-lived release branch, open a PR, wait for required
checks, then squash merge the PR. Do not use rebase merge for release
metadata PRs: GitHub may rewrite the commit SHA, which makes it too easy to tag
the pre-merge commit instead of the actual release-branch commit.
Remember that a GitHub squash merge creates a GitHub-authored commit on
RELEASE_BRANCH, not the original locally signed commit. If the user requires
the release metadata commit on the release branch itself to be locally signed,
use the direct-push path or ask before using the PR fallback.
git switch -c release/v<version>
git push -u origin release/v<version>
gh pr create --base <RELEASE_BRANCH> --head release/v<version> \
--title "chore(release): prepare v<version>" \
--body-file .local/PR-v<version>.md
gh pr checks <pr-number> --watch --interval 10
gh pr merge <pr-number> --squash --delete-branch
git fetch origin <RELEASE_BRANCH> --tags
git switch <RELEASE_BRANCH>
git pull --ff-only
After the direct push or squash merge, rerun the metadata gate on
RELEASE_BRANCH, then create exactly one tag on the actual release-branch
commit.
RELEASE_TAG=v<version> pnpm release:check
If signing tags is configured and works locally, prefer a signed annotated tag:
git tag -s v<version> -m "v<version>"
If signed tags are not available and the user approves an unsigned release tag, create a lightweight tag instead:
git tag v<version>
Do not silently fall back from a failed signed tag to an unsigned tag. Ask the
user which tag form to use. Before pushing, verify the tag points at
origin/<RELEASE_BRANCH> or the intended release-branch commit:
git tag -v v<version>
git merge-base --is-ancestor v<version> origin/<RELEASE_BRANCH>
Publish
Push the tag after the release metadata is already on RELEASE_BRANCH:
git push origin v<version>
The tag push triggers Release Desktop (macOS universal). The workflow must
pass Check release metadata in the no-secret Test and prepare signing input
job before the environment-gated Sign, notarize, publish job can request
approval and access Apple signing secrets.
For a manual dispatch, verify the tag already exists on GitHub:
git ls-remote --tags origin v<version>
gh workflow run release.yml --ref <RELEASE_BRANCH> -f tag=v<version>
Monitor And Verify
Find the run for the release tag and watch it. If it takes a while to appear, sleep for 5-10 minutes before deciding it failed to start.
gh run list --workflow release.yml --limit 10
gh run watch <run-id>
The Sign, notarize, publish job pauses for apple-signing Environment
approval. Treat that pause as expected. Before approving, verify the workflow
run is for the intended tag, the tag points at the intended default-branch
commit, and the version/changelog metadata match the tag.
If monitoring is delegated and the monitor stops at the approval gate, resume monitoring after approval. Do not end the release turn as "done" at the approval gate; the workflow still has to publish and verify release notes after assets are uploaded.
On failure, inspect logs yourself:
gh run view <run-id> --log-failed
After success, verify the release and generated assets:
gh release view v<version>
gh release download v<version> --dir .local/release/v<version>
ls .local/release/v<version>
Expect signed/notarized Universal macOS assets:
- A versioned Universal DMG, such as
PwrAgent-<version>-universal.dmg. - A stable
PwrAgent.dmgalias uploaded by the workflow forhttps://github.com/pwrdrvr/PwrAgent/releases/latest/download/PwrAgent.dmg. - A Universal updater ZIP and
.blockmap. latest-mac.yml.
The stable PwrAgent.dmg alias is intentionally unversioned so the website can
link to the latest release without knowing the current version. Do not remove
or replace it with an arch-suffixed DMG.
The workflow replaces electron-builder's empty/default release notes after
release assets are published. For beta releases that should be offered by the
normal updater and stable landing-page download URLs, leave GitHub's release
label as None so the release can become Latest; do not mark it as
Pre-release. GitHub excludes pre-release entries from /releases/latest,
which also breaks
https://github.com/pwrdrvr/PwrAgent/releases/latest/download/PwrAgent.dmg and
the default Electron updater feed.
This release-note publication is required, not cosmetic. Electron-builder may leave the body empty or duplicate the tag name. If the workflow release-notes job fails or GitHub temporarily rejects the edit, run the manual fallback after confirming the extracted notes match the approved changelog entry:
node scripts/extract-release-notes.mjs \
--tag v<version> \
--out .local/release/v<version>/RELEASE_NOTES.md
gh release edit v<version> \
--repo pwrdrvr/PwrAgent \
--notes-file .local/release/v<version>/RELEASE_NOTES.md
Only add --prerelease when the user explicitly wants a release hidden from
normal update checks and latest download links.
Verify the final release body before calling the release complete:
gh release view v<version> --json name,body,isPrerelease \
--jq '{name, isPrerelease, bodyLength: (.body | length)}'
The bodyLength must be greater than zero and the title/body must match the
approved changelog entry.
Local Fallback
Use the local path only when CI is unavailable or the user explicitly asks for local signing/notarization. Follow ../../../docs/desktop-release-runbook.md for required Apple and GitHub secrets.
pnpm --filter @pwragent/desktop package:dryrun
pnpm --filter @pwragent/desktop package
pnpm --filter @pwragent/desktop release