Release Patch — back-port a fix to an existing release line
SkillDev toolsBack-port fix(es) onto an existing release line and cut a patch release. Create the new release branch from the base with NO direct commits, land the fix(es) and the version+changelog bump via PRs into it, then publish via the `release` skill. Use for patching a shipped version (e.g. what an older SDK release pins) without pulling in later main-line changes.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Release Patch — back-port a fix to an existing release line skill
What this skill tells your AI
The instructions your AI receives, as published by tetherto/qvac in packages/ocr-ggml/.agent/skills/release-patch/SKILL.md and read by ahel’s review.
Cut a patch release on an existing release line (a shipped x.y.z a consumer still depends on)
by back-porting one or more already-merged fixes, without dragging in later main-line changes.
Use this when, e.g., an older SDK release pins @qvac/<pkg> ^0.36.3 and needs a fix that only landed
on main/a newer minor. The output is a new patch (e.g. 0.36.4) published under the line's
maintenance dist-tag — latest is left pointing at the newest version.
<package> is the directory name under packages/ (e.g. llm-llamacpp). <fix-PR-or-commit> is one
or more merged fix PRs (or their squash-merge SHAs). [base-release-branch] is the release line to
patch (e.g. release-llm-0.36.3); if omitted, discover it in Step 1.
Golden rule
Never commit or git push directly onto a release-* branch. The release-* ruleset requires all
changes to arrive via a merged PR (a bare branch creation push is allowed; subsequent commits are
rejected — GH013 … Changes must be made through a pull request). So: create the release branch as an
exact copy of the base, push it once, then land every change (fix cherry-picks, version bump) via
PRs whose base is that release branch.
Workflow
Step 1 — Identify the target release line + next version
- Determine the version the consumer needs — read its dependency spec, e.g. the SDK's
packages/sdk/package.json:"@qvac/<pkg>": "^0.36.3"→ the0.36line, and the patch must be>= 0.36.3 < 0.37.0. - Find the actual release-line ref. ⚠ Naming is not uniform. The
releaseskill's default isrelease-<package>-<version>/ tag<package>-v<version>, but real patch lines have used a short prefix: branchrelease-llm-<version>+ tagllamacpp-llm-v<version>. Do not assume — verify:
Pick the branch/tag that actually holds the target version (confirm withgit ls-remote origin 'release-*' | grep -i <pkg> # candidate branches git ls-remote --tags origin | grep -iE '<pkg>|llm' # candidate tagsgit show <ref>:packages/<pkg>/package.json | grep '"version"'). That commit is the base. - Next patch version = base patch + 1 (must satisfy the consumer range and stay below the next
minor). Record it as
<newver>and the base branch as<base>.
Step 2 — Resolve the source commits (use the SQUASH-merge SHAs on main)
For each fix PR, take the squash-merge commit on main, not the PR's individual branch commits:
gh pr view <N> --repo tetherto/qvac --json number,title,mergeCommit --jq '{n:.number,title,sha:.mergeCommit.oid}'
Do not cherry-pick a version/changelog-only PR (e.g. a prior x.y.z bump) — that bump is redone
for this line in Step 6.
Step 3 — Assess cherry-pick cleanliness BEFORE touching anything
For each source commit, diff its touched files between the base and the commit's parent:
git diff --numstat <base> <squash-sha>~1 -- <touched-paths>
Empty output ⇒ that file is identical at the base ⇒ the hunk applies cleanly. Non-empty ⇒ that file diverged ⇒ expect a conflict there (typically test files). If the core source files are identical, the back-port is safe; a few diverged test files are resolved by grafting in Step 5. If everything is heavily diverged, stop and reconsider (the fix may need manual porting, not a cherry-pick).
Step 4 — Create the release branch from the base — NO changes — and push it
git -C <repo> fetch origin <base>
git -C <repo> worktree add <wt> origin/<base>
git -C <wt> checkout -b release-<pkg>-<newver> # exact copy of the base, no edits
git -C <wt> config user.email "<you>"; git -C <wt> config user.name "<you>"
git -C <wt> push origin release-<pkg>-<newver> # branch-creation push (allowed)
Commit identity = the releaser; never add an AI signature / Co-Authored-By. Confirm HEAD == base
commit. Do not bump the version here — leave it at the base value (publish-safe).
Step 5 — PR #1: the fix(es), into the new release branch
Work on a normal-named branch (not release-*, so it isn't push-protected):
git -C <wt> checkout -b <TICKET>/backport-<pkg>-<newver>
git -C <wt> cherry-pick <squash-sha>...
Resolve only the expected diverged-file conflicts by grafting the commit's added block into the base file (keep the base file's structure; do not reformat unrelated lines). All identical-at-base files apply automatically.
Validate completeness (that the cherry-pick captured the source, nothing missed):
- Cleanly-applied files — resulting blob byte-identical to the source commit's post-image:
git rev-parse <squash-sha>:<file>==git rev-parse HEAD:<file>for each. - Manually-resolved files — the added lines equal the commit's added lines
(
git show <squash-sha> -- <file>+-lines vsgit diff <base> HEAD -- <file>+-lines).
Push the helper branch; open PR #1 with base = release-<pkg>-<newver> (four-emoji body; link the
source PR(s)).
CI: add the matrix labels — prebuilds, run-cpp-addon-tests, run-desktop-addon-tests,
run-mobile-addon-tests — to run the fix-validating jobs. Do not add
verified: ci-router reads only those four names, so verified selects no stage and fails
silently, costing a CI round. (The label still exists and its siblings' descriptions still read
"requires verified" — that text is stale; the label gate was retired, and
ci-trust-policy.test.mjs asserts it stays retired.) Auto-approve the release
environment deployment when it appears. Always read failures from full logs via
gh api repos/tetherto/qvac/actions/jobs/{id}/logs — gh run view --job --log silently truncates
(~1.1 MB of ~9 MB) and can fake a "hang". Triage flaky-vs-real; pre-existing environment failures
(e.g. test-darwin-x64 macOS-x64-VM timeouts) are non-gating and not blockers — confirm the fix's own
tests pass. Merge PR #1 into the release branch.
Step 6 — PR #2: version + CHANGELOG bump, into the release branch
On another helper branch off the (now fix-carrying) release branch:
packages/<pkg>/package.json: base version →<newver>.packages/<pkg>/CHANGELOG.md: add## [<newver>] - <YYYY-MM-DD>(bracketed heading — the release workflow requires this format) above the base entry; reuse the original fix's changelog wording, crediting the fix PR(s).package.jsonversion MUST equal the new heading.- Keep the version at the base value until this PR so nothing publishes early.
- Lint gate varies by line era — run
npm run lintinpackages/<pkg>(older release lines usestandard, newer useprettier); match whatever that branch enforces.
Open PR #2 (base = release-<pkg>-<newver>).
Step 7 — Release (merging PR #2 auto-triggers publish; use the release skill to verify)
Merging the bump PR into the release-* branch is a trusted push → on-merge-<pkg>.yml runs and
publishes automatically. No labels are needed on a merge/push: push is a trusted event in
ci-router, which short-circuits label parsing and enables every stage on its own. Publication is
gated by version>npm + release-merge-guard. Follow the release skill for the monitor/verify mechanics (its Steps 4–6),
with these nuances:
release-skill latest-guard caveat. Thereleaseskill's Step 1 compares the local version to npmlatestand stops if not higher. A back-port (e.g.0.36.4<latest0.38.0) fails that check — so the publish path for a maintenance patch is the on-merge auto-trigger from the PR merge, and you verify against the line's maintenance dist-tag, notlatest. Use thereleaseskill for its monitor/verify steps; don't let the latest-guard block a legitimate back-port.- dist-tag. The
npm-dist-tag-determinationaction publishes an older-than-latestpatch under a maintenance tag (e.g.release-<major.minor>) and must NOT movelatest. This is correct — consumer semver ranges (^0.36.3) resolve by version, so they still pick up the new patch. create-tagjob fails (HTTP 403/422). It usessecrets.GITHUB_TOKEN, which is not authorized to create the protected release tag (no repo tag-ruleset; a user/PAT can). So the git tag is NOT created automatically — create it manually with a user/PAT credential:
(Longer-term fix: changegit tag <pkg-tag-prefix>-v<newver> <release-branch-sha> # e.g. llamacpp-llm-v0.36.4 git push origin <pkg-tag-prefix>-v<newver>create-release-tag.ymlto usesecrets.PAT_TOKEN, likelabel-gatedoes.)
Step 8 — Verify
npm view @qvac/<pkg>@<newver> version→ the new patch exists.npm view @qvac/<pkg> dist-tags→latestunchanged; the new patch under its maintenance tag.- The release git tag exists.
- The consumer's range now resolves to the new patch (
npm view @qvac/<pkg>@'<range>' version).
Nuances & gotchas (all hit for real; do not relearn them)
- PR-only changes to
release-*— the ruleset blocks direct commits after branch creation. Land everything via PRs whose base is the release branch (helper branches are normal-named). - Cherry-pick the squash-merge SHA, not PR-branch commits.
gh run view --logtruncates — usegh api …/actions/jobs/{id}/logs; sanity-check job duration vs timeout before believing a "hang". (Seereference_gh_job_log_truncation.)- Merging into
release-*auto-publishes (on-merge, trusted push) — the version bump is the point-of-no-return; keep version at base until then. - Back-port dist-tag goes to a maintenance tag, never clobbering
latest. create-tag403/422 → create the tag manually (GITHUB_TOKEN can't; PAT/user can).release-skill Step 1 latest-guard doesn't fit back-ports — verify against the maintenance tag.- No AI signatures /
Co-Authored-Byin commits or PRs. - Lint gate era —
standardon older release lines,prettieron newer; runnpm run lint.
Worked example — @qvac/llm-llamacpp 0.36.4 (QVAC-22472)
SDK 0.15 pinned ^0.36.3; the n_predict-inside-reasoning fix was only on main/0.37+.
- Base: branch
release-llm-0.36.3= tagllamacpp-llm-v0.36.3(84be415c, v0.36.3). - Source: PR #3318 squash
0d782b12c(the fix). PR #3327 (0.37.1 bump) NOT cherry-picked. - Cleanliness: all C++/unit files identical at base; only
qwen3-5.test.jsdiverged → grafted the one new test block; blob-identity confirmed for the rest. - Branch
release-llm-0.36.4created from base (no changes) → PR #3337 (fix) → PR #3342 (0.36.4 bump). - Merge auto-published: run 29749326488; npm
0.36.4under dist-tagrelease-0.36;lateststayed0.38.0.create-tagfailed 403/422 → tagllamacpp-llm-v0.36.4created manually via SSH.
Error handling
- Heavy cherry-pick conflicts across core source (not just tests) → the fix predates too much divergence; stop and port it manually or reconsider the base.
release-merge-guardfails on merge → version not bumped or CHANGELOG heading missing/mis-formatted.- Publish didn't run after merge → confirm the merge was a push to
release-*andpackages/<pkg>/**changed; checkon-merge-<pkg>.ymlruns. - Publish ran but
latestmoved to the patch → the dist-tag logic mis-fired; restore withnpm dist-tag add @qvac/<pkg>@<real-latest> latest.
Signals
- GitHub stars
- 601
- Forks
- 111
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
release-patch- Source
- github.com/tetherto/qvac