---
name: hotfix
description: Promote an already-merged development fix to main and production through the gated Hub train.
---

**Host-native invocation:** Claude `/mmi:hotfix` · Codex `$mmi:hotfix` · Cursor `/hotfix` · jervcode `/hotfix` · Hermes skill_view. A backticked `/name` in this doc names the matching workflow (this skill or a sibling), not a literal command.

**Argument:** none. A hotfix is PATCH-only; leave `MMI_BUMP_INTENT` and `MMI_RELEASE_VERSION` unset — a non-patch declaration refuses.

# /hotfix — promote a development fix to main + prod

A hotfix is promotion, not authoring. The fix lands on `development` first through a normal issue, worktree, PR, review and CI gate; Hub then ports that merged PR or SHA onto a main-base branch as a cherry-pick with `-x`, keeps the audit trail, and performs the PATCH release. Never author the fix on a branch off `main`. The FIX is never back-merged: `development` already has it, `rc` absorbs it at the next `/rcand`, and the release hotfix-coverage guard blocks a later candidate that omits it. The VERSION FOLD is different: `hotfix release` ports it to `development` as a fold PR — NOT a back-merge, nothing from `main` is merged into `development`.

## Lane

Read META and name the lane before describing or executing anything:

```bash
mmi-cli oracle org project get {owner}/{repo} --json
```

A hotfix always skips rc on every track: it cherry-picks `development → main` directly, MMI-Hub included. `deployModel` decides the release-triggered runs the train watches; on `vercel` nothing is dispatched — Vercel builds `main` on the merge and the train reads its Production Deployment, same as `/release`. Unreadable META → stop; never infer the lane from branch names, tags or release history.

## Preflight

```bash
mmi-cli devops train doctor --lane hotfix --heal --json
```

Read `ready`, then `findings[]`: each carries `code`, `severity` (`blocker` · `warning` · `healed` · `info`), `source` (`local` · `origin`), `remedy` and `anchor`. `ready: false` → stop and quote every blocker's `code` and `remedy`; an unread origin is never green. `hotfix start` and `hotfix release` re-run this doctor as their step 0 with heal on.

## Apply

Run from the primary checkout. One cycle carries as many merged fixes as you name, in pick order.

1. Start from the merged development fix(es):

```bash
mkdir -p .jerv/tmp && r1=$(mktemp .jerv/tmp/hotfix-start.XXXXXX)
mmi-cli devops hotfix start --from <pr|sha>[,<pr|sha>…] --json --out "$r1"
```

`hotfix start` derives the PATCH from the latest Release tag on `origin/main`, branches `hotfix/vX.Y.Z` from `origin/main`, cherry-picks with `-x`, folds the version, pushes, and opens the main-base PR with its carries marker. Never duplicate those steps with raw branch, cherry-pick or PR commands.

2. Verify the main-base PR: the repo's local gate on the hotfix branch, the independent review the active agent doctrine requires, and the original issue's acceptance criteria. Then merge through Hub — it waits for required CI and demands the review verdict itself; the PR may squash, and the `-x` trailer must survive in the squash message:

```bash
mmi-cli devops pr merge <number> --squash
```

Because Hub's default branch is `development`, the main-base PR closes no tracking issue — the development PR already did; add no raw GitHub issue operations.

3. Release, naming every fix the cycle carries (each is proven an ancestor of the tagged SHA before tagging):

```bash
mkdir -p .jerv/tmp && r2=$(mktemp .jerv/tmp/hotfix-release.XXXXXX)
mmi-cli devops hotfix status --json
mmi-cli devops hotfix release vX.Y.Z --carries <pr|sha>[,<pr|sha>…] --json --out "$r2"
```

MMI-Hub adds `--announce-summary-file "$f"` with a fresh 3–6 line neutral summary — never a product or brand name; Hub scope is only `mutmutco/MMI-Hub`, never a product's board or `ds-propagate.yml`. `hotfix release` tags, publishes the Release, watches the release-triggered runs, announces once they are green (Hub), verifies distribution, and opens the `hotfix-fold/<tag>` development fold PR. Do not hand-dispatch a second publish or deploy path: duplicate release events race or double-publish.

## Verify

Slack announcements use the GitHub-authenticated Hub service and its server-side MMI vault access.
Developers need no local AWS tools, AWS identity, or shared Slack key. Preserve a failed summary
for the supported continuation; never replay a delivery whose result is uncertain.

Read the receipts, never the exit code (exit `1` = a follow-up failed after the tag; a pending follow-up exits `0`, so a clean exit alone never means every leg resolved):

- `runs[]` — the release-triggered runs on the tagged SHA (workflow, conclusion, url). Watch each non-success entry to conclusion: `gh run watch <run-id> --repo {owner}/{repo} --exit-status`. Name which run is red: `deploy.yml` / `publish.yml` / `tenant-deploy.yml` / `jerv-gateway` is a deploy or publish verdict; a `gate` push run is ordinary CI sharing the SHA. `jerv-gateway` is the operator-host deploy leg: it has no run id to watch, so read its `deployNote` / `ledger.phases.deploy` — `pending` there means production was never deployed and blocks the next train run.
- `foldStatus` + `foldNote` — the `hotfix-fold/<tag>` development PR; land it by true merge (Merge floor) and say whether it landed.
- `announceNote` (Hub), `verifyNote`, `checkout`, `localSync`, `ledger` — the durable phase legs; a pending or failed leg is unresolved, never done.

`mmi-cli devops hotfix status vX.Y.Z --json` re-derives the whole pipeline from live reads (`state`, `tagPushed`, `releaseExists`, `runs`, `alignmentStatus`, `npmVersion`, `next`). Then the bounded Latest Release read; its `tagName` must equal the tag:

```bash
gh api repos/{owner}/{repo}/releases/latest --jq '{tagName:.tag_name,targetCommitish:.target_commitish,publishedAt:.published_at,url:.html_url}'
```

Branch facts close the verdict: the tag commit is on `origin/main`; `development` already had the fix before the hotfix began; `rc` is untouched by design; the fold PR landed or is named as pending.

## Recovery

Every refusal and every doctor blocker names its `code` and `anchor`: open `docs/Guides/train-troubleshooting.md#<code>` and follow that entry, nothing else. That guide is test-parsed, never free text — the infra drift and plugin-set suites validate every anchor cited here and assert policy from its wording — so an edit to it re-runs the infra suite (`npm --prefix infra test`) in the same PR: a heading or slug change is a code change. The hotfix subcommands are idempotent, so after an interruption rerun the identical `mmi-cli devops hotfix release vX.Y.Z --carries …` — each effect is checked before it is re-attempted, and the ledger names that rerun as this lane's continuation; `--resume --watch --json` is the release and rcand ledgers' only continuation form and never a hotfix one. A refused pick, a port conflict, a red authority probe, a failed check, a missing carry, or `development fold port FAILED` each ends the run (Merge floor): fix the cause on `development` through a normal PR, then rerun from the port merge SHA — never improvise a merge from `main`.

A prior release's unresolved ledger also blocks this lane — and when its only unresolved leg is a content-caused failed deploy on the already-published tag, this lane is the fix's carrier: close that leg first with the master-approved `mmi-cli devops release --supersede-deploy --apply` (the cause fix must already be merged on `development`), then run the hotfix; the gate archives the closed ledger aside on its own.

## Merge floor

- **Never squash-merge a tagged commit (#3167).** A squash re-mints the SHA and orphans the tag from the branch lineage, so every tag-anchored check mis-reads forever after. Any merge that carries an already-tagged commit — the `main → development` roll-forward, an alignment PR, a hotfix fold PR — lands as a true merge: `mmi-cli devops pr merge <number> --auto --merge` (or `--wait --merge`); `--wait` alone still squashes. The hotfix main-base PR itself may squash: its tag lands after, on the merged `main` HEAD, and the `-x` trailer must survive the squash message.
- **A refused train is a stop, never a license to finish by hand.** Every fail-closed halt — doctor blocker, authority probe, coverage guard, untolerated conflict, required checks, stray tag, ledger — ends the run. Fix the cause on `development` through a normal PR and rerun the train; never hand-resolve on `main`, hand-cherry-pick, bare-push a train branch, ack to save time, or bypass a check.
- **Version probes anchor on the latest Release tag, not a branch (#2734).** A branch manifest or a stale local tag is not a version source; a surviving local tag silently mints the next version.
- **One train at a time on a repo (#5380).** Two overlapping runs race the shared tag namespace; the ledger refuses the second — never start one to help the first.
- **Tag and Release deletion are deletion-class:** the authorized human's explicit per-turn go, named in the guide entry that prescribes it.

## Report

Title first, verdict first. Name the resolved tag (never a placeholder like `vX.Y.0`) and the Release URL, the receipt's status fields (`releaseStatus`, `followUpStatus`, `deployStatus`, `promoted`), and every workflow run row with its conclusion and evidence — use its URL when emitted, otherwise the receipt's explicit no-run-URL note — saying which run is red, and whether it is a deploy/publish verdict or ordinary CI — `announceNote`, the alignment note (`devRollForward`, `rcAlignment`, or the hotfix fold PR and whether it landed), the `checkout` clause, and the sanctioned dispatch mechanism you used. A pending follow-up is reported as unverified, never as success. Product names appear only when releasing that product's repo.

## Retro

Before the final report, answer one question honestly: did this skill's own instructions misfire this run — ambiguous wording, a misleading message, or an environment failure it should have warned about? Process only, never the user's code or task. If yes, file one lesson and move on; a clean run is silent (hard cap: one per run). It lands on the owning board, deduplicated, and is fixed only through a reviewed PR — never edit the skill live. The retro is advisory: if the call fails, note it and continue. File through MMI with the command below. The amendment itself stays reference-free: the skill-drift gate rejects issue references in any section outside Merge floor, so carry the evidence in words or point at the guide entry — never inline reference markers.

```bash
mmi-cli learning skill-lesson --skill hotfix --title "<what misfired>" --body "<what; evidence; proposed amendment>"
```
