Skip to content
Back to skills

Qa Hotfix

ASecurity

[QA Methodology] Release a hotfix of an already-merged-and-released fix into the stable bundles the operator names, as patch releases cut from support/<X.Y> branches. Use when asked to 'выпустить хотфикс для релиза' / release a hotfix, backport a fix to a stable bundle, or cut a patch on a support line. Not for finding bundles that miss a patch (/qa-bundle-check) or delivering a released hotfix onto environments (/qa-hotfix-check). Gated writes; never auto-merges.

  • 2 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 20, 2026
developmentgobashvuerailstestinggitapifrontend

Works with

  • terminal
  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned October 5, 2026

npx -y skills add VirtoCommerce/vc-mcp-testing-module --skill qa-hotfix --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Qa Hotfix?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Qa Hotfix
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/virtocommerce-qa-hotfix/badge)](https://www.skillsdirectory.com/skills/virtocommerce-qa-hotfix)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: qa-hotfix
description: "[QA Methodology] Release a hotfix of an already-merged-and-released fix into the stable bundles the operator names, as patch releases cut from support/<X.Y> branches. Use when asked to 'выпустить хотфикс для релиза' / release a hotfix, backport a fix to a stable bundle, or cut a patch on a support line. Not for finding bundles that miss a patch (/qa-bundle-check) or delivering a released hotfix onto environments (/qa-hotfix-check). Gated writes; never auto-merges."
argument-hint: "VCST-XXXX [v12,v14] [--repo=<name>] [--pr=<ref>] [--dry-run]"
---

# Release a Hotfix into Frozen Bundles

A VirtoCommerce **stable bundle** (`vc-modules/bundles/vN`) pins every module, the Platform, and
the Theme to one frozen generation. When a fix lands on `dev` and ships in the current release, a
frozen bundle does **not** get it for free — the bundle intentionally trails `master`. To deliver
the fix to a frozen bundle you cut a **hotfix**: a new patch `X.Y.(Z+1)` on the bundle's existing
`X.Y` line, produced from a `support/<X.Y>` branch.

This skill productizes the manual flow Oleg described (choose branch → find commit → cherry-pick →
commit → "Release Hotfix"). Terminal entry: [`/qa-hotfix`](../../commands/qa-hotfix.md).

> Twin relationship: `/qa-hotfix` is to hotfix releases what [`/qa-bundle-check`](../qa-bundle-check/SKILL.md)
> is to hotfix *detection*. `bundle:check` tells you a bundle is **missing** a same-line patch;
> `qa-hotfix` **produces** that patch for a specific task. They share `config/module-repo-map.json`,
> the `GIT_TOKEN` auth model, and the same-line semantics.

## VirtoCommerce hotfix mechanics (verified 2026-06-23)

- **Branch convention:** `support/<major.minor>` (e.g. order `3.1000.3` → `support/3.1000`,
  catalog `3.904.11` → `support/3.904`). Confirmed via release `target_commitish`.
- **The "Release hotfix" workflow** is a `workflow_dispatch` shipped in every repo; the filename
  varies by repo kind (discover it by workflow **name** "Release hotfix", don't hardcode):
  - module repo → `module-release-hotfix.yml`
  - `vc-platform` → `platform-release-hotfix.yml`
  - `vc-frontend` → `theme-release-hotfix.yml`
  Run **on the support branch** with `incrementPatch=true` → publishes `X.Y.(Z+1)`,
  `makeLatest=false`, then auto-commits the bumped version back.
- **`gh` is not required** — writes go through local `git` + the token-authenticated
  GitHub REST API (`workflow_dispatch` = `POST /actions/workflows/{id}/dispatches`).

## Why a deterministic script (+ a thin agent layer)

Resolving "task → PR → commit", "is it merged & released", "which support branch & next patch" is
mechanical — parse JSON, query GitHub, compare integers. So:

- **`scripts/hotfix/hotfix-precheck.ts`** does all the read-only analysis and emits a per-bundle verdict.
- **`scripts/hotfix/hotfix-release.ts`** does the one deterministic write (dispatch the Release-hotfix
  workflow) and verifies the published patch contains the fix.
- The **agent/orchestrator** only does what needs judgment: read the JIRA task, run the cherry-pick
  (resolving conflicts), and gate every write behind explicit human confirmation.

## Step 1 — resolve the fix (linked PR is primary)

`GIT_TOKEN` is read from `.env.local` (5000 req/h; without it 60/h → likely rate-limited).
PR resolution order:

1. **PRIMARY — the PR linked to the task:** GitHub search `org:VirtoCommerce <TASK> type:pr`
   (unscoped, so it also matches PRs that carry the key only in a comment). A single product-repo
   hit (`vc-module-*`, `vc-platform`, `vc-frontend`) is used directly.
2. **FALLBACK — the JIRA description:** only when search finds nothing / can't disambiguate, parse
   the PR link out of the issue **description**. The
   script does this via JIRA REST when `JIRA_EMAIL` + `JIRA_API_TOKEN` are in `.env.local`; the
   `/qa-hotfix` command does it via the Atlassian MCP when running interactively.
3. **MANUAL — last resort:** `--pr=<owner/repo#num | url>` or `--repo=<name>`.

## Step 2 — gate the fix: MERGED and SHIPPED (run FIRST, no bundles yet)

This is the original "check the PR is merged and the release is out" step. Run the precheck **without**
`--bundles` — it does the gate phase only, then stops to ask for bundles:

```bash
npm run hotfix:precheck -- VCST-5082            # gates-only: PR → merged → shipped
# overrides if the linked-PR search needs help:
npx tsx scripts/hotfix/hotfix-precheck.ts VCST-5082 [--repo=<name>] [--pr=<owner/repo#num|url>] [--json]
```

1. The PR is **MERGED** (else STOP — merge first; a hotfix cherry-picks the merged commit).
2. The fix has **SHIPPED** in a normal release (the merge commit is contained in a published
   release tag) — else STOP, run the normal "Release" workflow on the base branch first
   (ask for the release to be cut first).

Both pass → the script prints `✓ Gates passed` and prompts for bundles → go to Step 3.

## Step 3 — establish which bundles are the latest stable (ASK, never assume)

Only after the gates pass. **The set of stable bundles is not fixed — it changes as new generations
ship.** `v12`/`v14` are only an illustrative pair; do **not** hardcode them.

1. Ask the user: **"which release bundles are currently the latest stable?"** (the operator
   names them — e.g. "v12 and v14"). Accept whatever they name (`v13,v15`, a single `v16`, …).
2. If unsure what exists, candidates live at `vc-modules/bundles/<vN>/package.json` on `master` —
   but *which are the supported stable lines* is the user's call, not a guess. When in doubt, ask.

Every `vN` below is a placeholder for whatever the user named.

## Step 4 — per-bundle precheck (read-only)

```bash
npm run hotfix:precheck -- VCST-5082 --bundles=<the bundles the user named>
```

Per bundle: pinned version → line `X.Y` → does `support/X.Y` exist → highest patch on the line →
the patch a hotfix would produce (`highest + 1`) → whether the fix is already on the branch.

**Two layers of "can we hotfix this?":**
1. **By branch (physical possibility):** the `Possible?` column — a hotfix runs on `support/X.Y`.
   If the branch is missing → `✗ no` → **create it first** (gated write step 0 below) from the
   line's base tag, then proceed. The pinned tag must be real on that line (that's the base).
2. **By code (will it apply):** the `Code check` block — every file the fix MODIFIES/REMOVES must
   still exist on the support branch (added files are excluded). All present → clean cherry-pick
   likely; a missing file → the line diverged → cherry-pick will likely conflict. This is a
   no-clone heuristic; the **definitive** conflict check is the actual `git cherry-pick` at the
   write step (clone in `.fix-workspace/`, conflict → STOP, never force a risky resolution).

### The fix-shape gate — is this fix safe to hotfix? (developer's checklist)

Before ANY hotfix, the fix itself must clear the checklist a VC release engineer runs by hand. The
precheck prints it as the **`Fix-shape check`** block (computed once from the fix commit's own diff,
so it's repo/task-level, not per-bundle):

| # | Check | How it's verified | On FAIL |
|---|-------|-------------------|---------|
| 1 | **Fix in a single module** | the diff stays inside one `src/<Project>` tree | multi-project → **STOP + hand off** |
| 2 | **No breaking changes** | HEURISTIC flag: touches a contract-bearing file (`module.manifest`, `.csproj`, `Directory.Build.props`, `*Dto.cs`, `Models/`, `Contracts/`, `I*.cs`, `*Client.cs`) | flagged → a **developer MUST confirm** it isn't breaking before proceeding |
| 3 | **Doesn't bump other modules' dependency versions** | scans the diff for a changed `VirtoCommerce.*` version in a manifest/`.csproj`/props | a raised pin → **STOP + hand off** (a hotfix must not drag dependency versions) |
| 4 | **cherry-pick applies clean** | the actual `git cherry-pick` at the write step | conflict beyond trivial → **STOP + hand off** |
| 5 | **`vc-build compress` passes** | the **"Release hotfix" workflow** builds + tests the artifact; `hotfix:release --poll` exits `1` if the run is red | red run → **STOP**: report the run URL + failing step; a re-run needs a new confirmation |
| 6 | **Regression environment** | after the release deploys, run regression on the support line | RED → hand off |

Checks 1–3 are mechanized in the precheck (no clone); 4–6 are enforced downstream (write step /
release workflow / regression). **Golden rule (Oleg): if anything looks like it could break —
STOP, analyze, and involve a developer.** A `⚠` on check 1 or 3 makes the precheck exit `1` and
**suppresses the "Next steps" write plan** — it is not a hotfix candidate until a human clears it.
A `⚠` on check 2 is advisory (heuristic) but still requires a developer's explicit "not breaking"
before you proceed.

### Verdicts & exit codes

| Verdict | Meaning |
|---|---|
| `✓ READY → … → X.Y.(Z+1)` | support branch exists, fix not yet on it — proceed to the write steps |
| `◯ already-applied` | the fix is already on `support/X.Y` — nothing to cherry-pick. Established in three layers, and the badge names which one fired (`sha` / `trailer` / `content` match): the original commit is an ancestor of the branch · a `cherry-pick -x` trailer on it names the original SHA · **or the fix’s own diff is already present in the branch’s files**. Content is the layer that fires after a plain `git cherry-pick` — which rewrites the SHA and leaves no trailer — so without it a just-published hotfix keeps reading `READY` and invites a duplicate release of a shipped fix (measured on VCST-5940, 2026-09-10). Content answers *is this change present*, not *did we pick it*: true because the line never had the bug is the same operational answer. A binary or too-large diff leaves it inconclusive, which reads as `READY` — never as applied |
| `✗ no support/X.Y` | the branch doesn't exist yet — **create it first** (gated write step 0), branching from the line's base tag, then proceed with the hotfix |
| `— not in bundle` | the repo isn't pinned in that bundle — nothing to hotfix there |

- Exit `0` = every requested bundle is `ready`/`already-applied` · `1` = a gate is blocked (incl. a
  fix-shape signal — multi-module or dependency bump) · `2` = tool error.
- `--json` emits `{ task, repo, fixSha, prGate, releaseGate, fixShape, bundles[] }` for the orchestrator
  (`fixShape` = `{ modules, multiModule, dependencyBumps[], contractFiles[] }`).

## The write steps (gated — confirm before EACH)

For `READY` bundles, and for `✗ no support/X.Y` bundles after their branch is created (step 0).
Work in `.fix-workspace/` (gitignored). **Triple-guarded no-auto-merge culture applies**
(`.claude/knowledge/execution/quality-gates.md`): every write needs explicit human confirmation — sequentially,
one confirmation per write; in parallel, a single **batch** confirmation covering all lanes before
any push (see [`parallel-lanes.md`](parallel-lanes.md) §Parallel vs sequential). Either way, nothing is pushed unconfirmed.

**Step 0 — create the support branch when it's missing (`✗ no support/X.Y`).** A hotfix needs a
`support/X.Y` line; if it doesn't exist yet, create it (this used to be a hand-off, now it's a
gated write). Branch from the line's **base tag** = the highest released `X.Y.*` tag, or — if the
bundle's pinned `X.Y.Z` is all that exists on the line — the pinned tag itself. Never branch from
`dev`/`master` (that would pull in unreleased work).

```bash
# base = highest existing X.Y.* release tag (fallback: the bundle-pinned X.Y.Z)
git fetch origin --tags
git branch support/X.Y <base-tag>          # e.g. git branch support/3.1011 3.1011.0
git push origin support/X.Y                # ← confirm before this push
```

Confirm the base tag with the user before pushing (it decides what the new support line contains).
After the branch exists, re-run the precheck for that bundle → it should now read `✓ READY`, then
continue with steps 1–4.

For each ready bundle (line `X.Y`) — one **lane**; with ≥2 `READY` bundles run the lanes in
parallel (own worktree each) per [`parallel-lanes.md`](parallel-lanes.md) §Parallel vs sequential, after a single batch confirmation:

1. `git fetch && git checkout support/X.Y`
2. `git cherry-pick <fixSha>`
   - **Conflict:** resolve only if trivially mechanical; anything risky → **STOP + hand off**
     (the fix touched code that diverged on the support line — a human must decide).
3. Show the diff → **confirm** → `git push origin support/X.Y`
4. **confirm** → trigger + verify the release:
   ```bash
   npm run hotfix:release -- --repo=<name> --branch=support/X.Y --expect-commit=<fixSha> --poll
   ```
   `scripts/hotfix/hotfix-release.ts` discovers the "Release hotfix" workflow, dispatches it on the support
   branch (`incrementPatch=true`), polls the run, and verifies the published `X.Y.(Z+1)` release
   contains `<fixSha>`. Exit `0` = released + verified · `1` = run failed or commit not in release.

**Parallel lanes.** With ≥2 `READY` bundles, read [`parallel-lanes.md`](parallel-lanes.md) §Parallel vs
sequential before the batch confirmation — what runs concurrently, what stays serial, lane isolation.

### After the hotfix

- Re-run the precheck → the bundle should now read `◯ already-applied`.
- **Confirm before the tracker writes.** The outcome comment, the "Need hotfixes" flag and the status
  transition below are outward writes to a shared board: show the comment text, the flag and the
  target status, and get one explicit yes before writing any of them. On a no, write nothing and say
  so in the close-out.
- Report per bundle: new patch version + release URL. Comment the outcome on the JIRA task (English; Markdown, never Jira wiki markup; clear/brief/outcome-first per `knowledge/execution/tracker-ops.md` §5a **Comment & body style**).
- **Advance the JIRA status to `Hotfix ready`** (right after the outcome comment) — **only for
  issue type `Bug`.** The `Wait hotfixes` / `Hotfix ready` statuses live only in the Bug workflow;
  a hotfix can also target a **Story** (or any other type), and those have no such statuses — for
  them **leave the status untouched** (still post the outcome comment, still skip the flag). Check
  the issue's `issuetype.name` first; if it isn't `Bug`, do nothing here. For a Bug, the path is
  **`Tested → Wait hotfixes → Hotfix ready`**, but the middle hop is driven by a field, not a
  transition:
  1. **Set the "Need hotfixes" flag** (find the field by its name `Need hotfixes` in the issue's edit
     metadata and set its `true` option; on VCST that is `customfield_10181` = option `{id: "10151"}`,
     an example only — never hardcode it). A JIRA post-function then **auto-moves `Tested → Wait hotfixes`** (the
     "Wait hotfixes" status only exists while this flag is set — that's why there is no manual
     `Tested → Wait hotfixes` transition).
  2. **Take the live-discovered transition whose target status is `Hotfix ready`** (VCST: the
     "Hotfix released" transition, id `15`). Discover it with `getTransitionsForJiraIssue` — never
     hardcode the id; match by `to.name === "Hotfix ready"`.
  - **Never move a ticket backwards.** If the ticket is already at or past `Hotfix ready` (e.g.
    `Testing on stable`, `Done`), no `Hotfix ready` transition is offered → leave the status as-is.
    Setting the flag on such a ticket is harmless (it still needed hotfixes) but do not force a
    backward transition.
  - Tracker-agnostic: this is the VCST (Jira) workflow; discover the field + transitions live and
    skip the step on a tracker/project that has no `Hotfix ready` status.
- **Theme/frontend caveat:** a vc-frontend hotfix release asset is named `vc-frontend-X.Y.Z.zip`
  (not `vc-theme-b2b-vue-*`). If a bundle's
  `ThemeB2BVue` pin must then be bumped, take the URL from the release's real `assets[]`, never by
  string-replacing the version. (Bumping the bundle pin itself is `/qa-bundle-check` territory.)

## Final step — offer self-diagnostics (consent-gated)

At the very end of every run — released, STOPPED or BAILed — follow
[`self-check-offer.md`](self-check-offer.md): one Yes/No, run `/vc-self-check` only on an explicit Yes,
never auto-trigger.

## Hard rules (STOP/BAIL is a success, not a failure)

- **Never auto-merge.** The pipeline ends at a published hotfix release; merging the *bundle bump*
  PR (if any) is a separate human action. `merge_pull_request` / `gh pr merge` are denied.
- **Create a missing `support/X.Y` branch (gated), don't hand off.** When the line has no support
  branch, create it from the line's **base tag** (highest released `X.Y.*`, else the bundle-pinned
  `X.Y.Z`) with explicit human confirmation of that base — never silently, and never off
  `dev`/`master`. (Write step 0.)
- **One repo / one module per task.** Multi-repo or multi-module fixes → STOP + hand off (fix-shape
  check 1).
- **A hotfix never bumps a dependency version.** A raised `VirtoCommerce.*` pin (fix-shape check 3)
  → STOP + hand off — bumping the bundle's pins is `/qa-bundle-check` territory, not a hotfix.
- **STOP on:** PR not merged, fix not released, a fix-shape signal (multi-module / dependency bump /
  unconfirmed breaking-change surface), cherry-pick conflict beyond trivial, failed release run, or
  the published patch not containing the fix. Leave the JIRA task where it was with a one-line reason.
- **If anything looks like it could break — think first.** Analyze it and involve a developer before
  hotfixing; do not force a risky change onto a frozen support line.

## Reporting

This is tooling output, not one of the tracked report categories (`.claude/rules/reports.md` §1)
— print the precheck table and the released versions to the user / JIRA comment; do **not** create a
file under `reports/`. A hotfix that changes a release decision flows into the normal release report.

## Maintenance

- `config/module-repo-map.json` (shared with `bundle:check`) maps bundle module Id → repo and
  self-heals for new modules.
- Tag scheme assumed: bare `Major.Minor.Patch`; branch scheme `support/Major.Minor`. If a repo
  diverges (e.g. `v`-prefixed tags, a different support-branch name), adjust `tagOf()` / the
  `support/${line}` construction in `scripts/hotfix/hotfix-precheck.ts`.
- The "Release hotfix" workflow is discovered by name, so a renamed file keeps working as long as
  the workflow's `name:` stays "Release hotfix".

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…