hotfix · diff

git:20260926.85fc6e6 to git:20260927.1abc41d

37 added, 32 removed. Audit A to A.

---
name: hotfix
description: "Fast path for an urgent production fix — reproduce with a failing test, minimal fix on a hotfix branch from the release tag, mandatory security review for sensitive paths, expedited checklist, deploy and backport to main. Use for P1 production bugs."
argument-hint: "[issue description or bug id] | --chore <what>"
user-invocable: true
allowed-tools: Read, Glob, Grep, Write, Edit, Bash, Task, Skill, AskUserQuestion
- model: sonnet
---
# Hotfix
- Reply in the project conversation language (CLAUDE.md → Language); code, identifiers, paths and commit messages stay in English.
-
- File writes and any mutation (git, deploy) happen only after an explicit "May I write?" / "Proceed?" — each one `AskUserQuestion` (proceed (Recommended) · show the draft/diff first · not now) → "yes". After the "write" answer: `touch .claude/.write-consent` (rule 7 — the consent-guard hook checks the marker). Subagents spawned through `Task` cannot ask the user: the consent is collected here, and an engineer that needs more than its brief reports back so the parent asks.
-
- In the commands below, `<hooks>` is `.claude/hooks/` in copy mode and `${CLAUDE_PLUGIN_ROOT}/hooks/` in plugin mode; `<default>` is the default branch (`master`/`main`). **An open gate survives the next turn** (rule 7): before each commit, tag or push gate below, the skill records it — `<hooks>session-state.sh set Task "/hotfix <slug>" Gate "/hotfix Phase N: <question>"` — and clears it after the answer (`<hooks>session-state.sh set Gate "—"`), so a session that resumes with the `Gate:` line continues at that question instead of reading a new task from `Next:`.
+ Language, `<hooks>`, `<default>`, agent and command namespaces, gate mechanics (draft → "May I write?" → `Write`/`Edit` → `touch .claude/.write-consent`), subagent consent, the `Skill` tool and the CI wait: `docs/coordination-rules.md` § Skill conventions (engineers and reviewers: § Subagents).
- **The parent writes no code** — not the fix and not the failing test either: both go through the relevant engineer via `Task` with a studio `subagent_type` (`web-studio:<name>` in plugin mode, `<name>` in copy mode; coordination-rules § Subagents). A cut-off engineer is resumed from its `Checkpoint:`, never replaced.
+ **The parent writes no code** — neither the fix nor the failing test: both through the relevant engineer via `Task` with a studio `subagent_type`; a cut-off engineer is resumed from its `Checkpoint:`, never replaced. Every mutation (write, commit, tag, push, deploy) has its own `AskUserQuestion`.
- **Other studio skills run through the `Skill` tool** (`/web-studio:<name>` in plugin mode, `/<name>` in copy mode): `/code-review --diff` (Phase 2 and `--chore`), `/changelog` and `/deploy` (Phase 3). Each keeps all of its own phases and gates — its write gate, its commit gate, its confirmation before a production mutation — and its verdict comes back as text this skill quotes. They run one after another, after the running `Task` has returned, never two at once. `/incident` is a hand-off in the closing `AskUserQuestion`, not run from here.
+ **Through the `Skill` tool** (namespaced): `/code-review --diff` (step 2.5, `--chore`), `/changelog` and `/deploy` (Phase 3) — each with all its own phases and gates, one after another once the running `Task` has returned, its verdict quoted. `/incident` is a hand-off in the closing question, never run here.
- **Two kinds of urgent, one skill.**
- - `production` (the default): something is broken for users. Phases 1–4 below: reproduce with a failing test, minimal fix on a hotfix branch from the release tag, expedited security gate, release.
- - `--chore`: the toolchain is broken or in the way — a red runner, a linter that blocks every commit, a dependency that must move now. It skips the release machinery and follows the chore/infra lane of `git-workflow.md`:
- 1. branch `chore/<slug>` (`git fetch origin`, then `git switch -c chore/<slug> origin/<default>`);
- 2. commits `ci(…)` / `chore(…)`, each behind one commit gate recorded in the session state as above;
- 3. a PR with `/web-studio:code-review --diff` (copy mode `/code-review --diff`) run through the `Skill` tool before the merge (workflow files → `devops-engineer`); experimental commits are squashed or rebased away before the merge;
- 4. the outcome recorded as a finding (`production/findings.md`) or a backlog entry (`production/backlog.md`).
+ Glossary:
+ - `<tag>` — the release currently deployed (the newest `production/releases/vX.Y.Z.md` with a `DEPLOYED` line, else the newest `v*` tag after `git fetch origin --tags`; none → `BLOCKED (no release tag — nothing is deployed)`); `vX.Y.Z` — its patch number + 1.
+ - `<slug>` — the bug in a few words (`hotfix/<slug>`); `<scope>` — the scope of the fix commit `fix(<scope>): <bug>`, which `/code-review` uses where a story would give `S-NNN`.
+ - **Gate recording** — before every commit, tag or push question (each step below names its `Gate "…"` string): `<hooks>session-state.sh set Task "/hotfix <slug>" Gate "/hotfix Phase N: <question>"`; after the answer `<hooks>session-state.sh set Gate "—"`. An open gate survives the turn: a resumed session continues at that question, never at `Next:` (rule 7).
- Neither path is a place for a feature: work that changes what the product does is a story, however small it looks at the moment it is asked for.
+ **Two kinds of urgent, one skill.** `production` (default): broken for users — Phases 1–4. `--chore`: the toolchain is broken or in the way (a red runner, a blocking linter, a dependency that must move now) — read and follow `references/chore-lane.md`; Phases 1–4 do not run, verdict `DONE (chore — PR open)`. Neither path is a place for a feature: what changes the product's behaviour is a story, however small it looks.
## Phase 1: Reproduce
- 1. **Branch** `hotfix/<slug>` from the production tag (the release currently deployed): `git fetch origin --tags`, then `git switch -c hotfix/<slug> <tag>`. The branch starts on a commit that is already in `origin/<default>`; the commit hook (`validate-commit.sh`) is silent on `hotfix/*` branches, so its "already merged into origin/<default>" warning is not raised against the first commit here.
- 2. **A failing test** that reproduces the bug (mandatory), written by the relevant engineer through `Task` — the brief names the bug, the file the test goes into and the command that must go red; the parent writes no test itself (protocol). The engineer reports the red run's output, quoted in the report. No reproduction → stop with `BLOCKED (cannot reproduce — …)`: no fix is written without a failing test.
- 3. **Impact assessment**: data? security? → `security-lead` via Task.
+ 1. **Branch** `hotfix/<slug>` from `<tag>`: `git fetch origin --tags`, then `git switch -c hotfix/<slug> <tag>`. The commit hook (`validate-commit.sh`) is silent on `hotfix/*`: no "already merged into origin/<default>" warning on the first commit.
+ 2. **A failing test** that reproduces the bug (mandatory), by the relevant engineer through `Task` — the brief names the bug, the test file and the command that must go red; the red run's output is quoted in the report. No reproduction → `BLOCKED (cannot reproduce — …)`: no fix without a failing test, nothing written.
+ 3. **Impact**: data or security involved → one `Task` to `security-lead` with the bug and the files; its answer goes into the engineer's brief (step 2.1) and says whether step 2.5 applies. It annotates, never blocks.
## Phase 2: Minimal fix
- 1. **Through the relevant engineer**: `Task` with the studio `subagent_type`. The parent writes no code; a cut-off engineer is resumed from its `Checkpoint:` (coordination-rules § Subagents).
- 2. **Only what is needed.** A fix that needs a DB migration is not written straight away: the engineer reports it back, and the parent warns the user before it is written — a rollback restores the image, not the data, so the migration must be reversible or the hotfix goes forward only.
+ 1. **Through the relevant engineer**: `Task` with the studio `subagent_type`; the brief holds the test of step 1.2 and the impact of step 1.3.
+ 2. **Only what is needed.** A fix that needs a DB migration is not written straight away: the engineer reports it and the parent warns the user first — a rollback restores the image, not the data, so the migration must be reversible or the hotfix goes forward only.
3. **Checks**: test green; lint/typecheck.
- 4. **Fix commit and push**, one `AskUserQuestion` recorded first as `Gate "/hotfix Phase 2: commit and push the fix?"` and cleared after the answer: commit and push (Recommended) · commit only · show the diff · not now. On "yes": `git commit -m "fix(<scope>): <bug>"` staging the fix and its test by name, on the hotfix branch, then `git push -u origin hotfix/<slug>` — the branch gets its upstream here, before any review, so a review fix can be pushed. "Commit only" → the push happens at the backport step with its own consent, and the report says the branch is local until then.
- 5. **Sensitive paths** (the `paths:` of `rules/security-sensitive.md`: auth, security, middleware, proxy config, payments, uploads, webhooks): `appsec-engineer` review — `/web-studio:code-review --diff --security` (copy mode `/code-review --diff --security`) through the `Skill` tool, after the fix commit of step 4, so the review and its `appsec-engineer` run are in the audit log and the review sees the fix; its verdict is quoted. `NEEDS CHANGES` → the fixes go back through the engineer inside `/code-review`'s own fix gate, which commits them on the hotfix branch as `fix(<scope>): apply /code-review findings` (there is no `S-NNN` here: `/code-review` takes the scope from the fix commit) and pushes them to the upstream set in step 4; nothing goes on to Phase 3 before the reviewers answer `APPROVED`.
+ 4. **Fix commit and push** — `Gate "/hotfix Phase 2: commit and push the fix?"`, one `AskUserQuestion`: commit and push (Recommended) · commit only · show the diff · not now. On "yes": `git commit -m "fix(<scope>): <bug>"` staging the fix and its test by name, on the hotfix branch, then `git push -u origin hotfix/<slug>` — the upstream exists before any review, so a review fix can be pushed. "Commit only" → the push happens at step 3.7 with its own consent; the report says the branch is local until then.
+ 5. **Sensitive paths** (the `paths:` of `rules/security-sensitive.md`: auth, security, middleware, proxy config, payments, uploads, webhooks):
+ 1. Decide from the files of the fix commit; none sensitive → Phase 3.
+ 2. `/web-studio:code-review --diff --security` (copy mode `/code-review --diff --security`) through the `Skill` tool, after the commit of step 2.4, so the review sees the fix and its `appsec-engineer` run is in the audit log; its verdict is quoted.
+ 3. `NEEDS CHANGES` → the fixes go through the engineer inside `/code-review`'s own fix gate, which commits `fix(<scope>): apply /code-review findings` (no `S-NNN`) on the hotfix branch and pushes to the upstream of step 2.4 — after "commit only" it commits locally and says so.
+ 4. Phase 3 starts only when the reviewers answer `APPROVED`.
## Phase 3: Expedited gate
- 1. Package tests + e2e smoke.
- 2. **`/changelog`** through the `Skill` tool: the patch version `vX.Y.Z` (the deployed tag's patch number + 1) with its own "May I write `CHANGELOG.md`?" gate and its own commit gate — the only gate for `CHANGELOG.md`; this skill adds none. `/changelog`'s gate recognises a `hotfix/*` HEAD and offers to commit there (Recommended — the patch tag must contain it): the `docs: changelog vX.Y.Z` commit lands on the hotfix branch, and the tag below must contain it. A changelog left uncommitted, or committed on another branch, is named in the report: the tag would not carry it, so the tag question is not asked before that is put right.
- 3. **Release file** `production/releases/vX.Y.Z.md`, the minimal one `/deploy` Phase 1 reads (it stops with `BLOCKED (no release file — …)` without it, and `/release-checklist` is not run for a hotfix): what changed (the bug and the fix in one paragraph), the fix commit (its hash, and the review-fix commit when there is one), the tag `vX.Y.Z`, the CI run that builds the image (the workflow file, and the run id in the report once the tag is pushed), the rollback tag (the deployed one), and the line `Verdict: READY (hotfix)`; the `/release-checklist` gates a hotfix skips are listed as skipped, never ticked. Show the draft, then "May I write `production/releases/vX.Y.Z.md`?" — one `AskUserQuestion`: write (Recommended) · show the draft/diff first · not now; after the "write" answer `touch .claude/.write-consent`. Right after the write, one commit gate, recorded first as `Gate "/hotfix Phase 3: commit the release file?"` and cleared after the answer: `docs: release vX.Y.Z (hotfix)` staging exactly that file, on the hotfix branch (commit (Recommended — the tag must contain it) · leave uncommitted). An uncommitted release file → no tag; say why.
- 4. **Patch tag**, created and pushed by this session, never by a subagent, and as its own `AskUserQuestion`, recorded first as `Gate "/hotfix Phase 3: tag vX.Y.Z and push it?"` and cleared after the answer: tag `vX.Y.Z` on the hotfix branch and push it now (Recommended) · not now. On "yes": `git tag -a vX.Y.Z -m "Hotfix vX.Y.Z: <bug>" && git push origin vX.Y.Z` (without `-m`, `git tag -a` opens an editor the session cannot use). A hotfix tag is the documented exception to "tags on the default branch" (git-workflow: a hotfix branches from the release tag and is backported afterwards); the tag sits on the release-file commit, which sits on the changelog commit, which sits on the fix commit. "Not now" → the report says the release is untagged and `/deploy` cannot run.
- 5. **CI builds the image from the tag.** Find out what starts on the tag before waiting for anything (as `/dev-story` Phase 6 step 6 checks `pull_request`): `grep -l "tags:" .github/workflows/*.yml` and each such workflow's `on: push: tags:` pattern — the tag must match it (`v*` matches `vX.Y.Z`). With a matching workflow, wait for the tag's run before deploying — one background `gh run watch <run-id> --exit-status` (as `/story-done`), only for a run that exists; no polling, no `AskUserQuestion` as a pause. Red → `BLOCKED (CI red on vX.Y.Z)`, nothing deployed. Without a tag trigger, nothing starts on the tag: say so, name the run to expect instead (a `push` run on the branch, or a `workflow_dispatch` the user starts by hand) or that no run starts at all, and that `/deploy` will then find no green run on the tag. Without `gh`, name the run to check by hand.
- 6. **`/deploy vX.Y.Z`** through the `Skill` tool, with its own confirmation before the production mutation (delegate verbs `deploy`/`rollback` by `.claude/docs/deploy-target-contract.md`); it reads the release file's `Verdict:` line, verifies the tag, the green run and the image itself. Its verdict line (`DEPLOYED vX.Y.Z` | `FAILED (…)`) is quoted in the report.
- 7. **Backport** to the default branch (`<default>`, `master`/`main`) through a PR: the branch is already on origin when step 2.4 pushed it (otherwise `git push -u origin hotfix/<slug>` now, with consent recorded as `Gate "/hotfix Phase 3: push hotfix/<slug>?"` and cleared after the answer), then `gh pr create --fill --base <default>`; the merge follows the PR's review, never a direct push.
+ 1. **Tests**: package tests and the e2e smoke, with the commands `docs/architecture/test-strategy.md` names per level (else `technical-preferences.md` or the `Makefile`). Red → `BLOCKED (tests red — <command>)`, nothing released.
+ 2. **`/changelog`** through the `Skill` tool for `vX.Y.Z`, with its own "May I write `CHANGELOG.md`?" gate and its own commit gate — the only gate for `CHANGELOG.md`; this skill asks no second one. That gate offers the `hotfix/*` HEAD (Recommended — the tag must contain it): `docs: changelog vX.Y.Z` lands on the hotfix branch, HEAD never leaves it. A changelog uncommitted or committed elsewhere is named in the report, and the tag question waits until that is put right.
+ 3. **Release file** `production/releases/vX.Y.Z.md` — the minimal one `/deploy` reads (without it: `BLOCKED (no release file — …)`); `/release-checklist` is not run for a hotfix.
+ 1. Content: what changed (the bug and the fix in one paragraph); the fix commit (its hash, plus the review-fix commit when there is one); the tag `vX.Y.Z`; the CI run that builds the image (the workflow file; the run id in the report once the tag is pushed); the rollback tag (`<tag>`); the line `Verdict: READY (hotfix)`; the `/release-checklist` gates a hotfix skips, listed as skipped, never ticked.
+ 2. Show the draft, then "May I write `production/releases/vX.Y.Z.md`?" — one `AskUserQuestion`: write (Recommended) · show the draft/diff first · not now; after the "write" answer `touch .claude/.write-consent`.
+ 3. Right after the write, one commit gate — `Gate "/hotfix Phase 3: commit the release file?"`: `docs: release vX.Y.Z (hotfix)` staging exactly that file, on the hotfix branch (commit (Recommended — the tag must contain it) · leave uncommitted). Uncommitted → no tag; say why.
+ 4. **Patch tag**, created and pushed by this session, never by a subagent, as its own `AskUserQuestion` — `Gate "/hotfix Phase 3: tag vX.Y.Z and push it?"`: tag `vX.Y.Z` on the hotfix branch and push it now (Recommended) · not now. On "yes": `git tag -a vX.Y.Z -m "Hotfix vX.Y.Z: <bug>" && git push origin vX.Y.Z` (without `-m`, `git tag -a` opens an editor the session cannot use) on the release-file commit, so the tag contains the changelog and the release file — the documented exception to "tags on the default branch" (git-workflow.md). "Not now" → the report says the release is untagged and `/deploy` cannot run.
+ 5. **CI on the tag**: read `references/ci-on-tag.md` and follow it — triggers read first, only an existing run waited for. Green → step 3.6; red → `BLOCKED (CI red on vX.Y.Z)`, nothing deployed; no tag trigger → name what runs instead and that `/deploy` will find no green run on the tag.
+ 6. **`/deploy vX.Y.Z`** through the `Skill` tool, with its own confirmation before the production mutation (delegate contract: `.claude/docs/deploy-target-contract.md`); it reads the release file's `Verdict:` line and verifies the tag, the green run and the image. Its verdict line (`DEPLOYED vX.Y.Z` | `FAILED (…)`) is quoted in the report.
+ 7. **Backport PR** to `<default>`. The branch is on origin when step 2.4 pushed it; otherwise `git push -u origin hotfix/<slug>` now, behind its own question — `Gate "/hotfix Phase 3: push hotfix/<slug>?"`. Then `gh pr create --fill --base <default>`; the merge follows the PR's review, never a direct push. Skipped on "not now" to that push or without `gh` (name the PR to open by hand); the closing question then offers the backport.
## Phase 4: Postmortem note
- A short entry in `docs/ops/incidents/` (or `/incident` if there was an incident), behind "May I write `docs/ops/incidents/<file>`?" — one `AskUserQuestion`: write (Recommended) · show the draft first · not now; after the "write" answer `touch .claude/.write-consent`. Right after the write, one commit gate (rule 7 (4), `git-workflow.md` § Documents), recorded first as `Gate "/hotfix Phase 4: commit the postmortem note?"` and cleared after the answer: `docs: incident <slug>` staging exactly that file. HEAD is the hotfix branch here, so name it and offer: switch to the default branch and commit there (Recommended — a postmortem is a pipeline-wide document; `git switch <default> && git pull --ff-only origin <default>` first, and back to `hotfix/<slug>` afterwards while the backport PR is open) · commit here (it rides the backport PR) · leave uncommitted. Nothing is committed without the answer; the fix never rides the `docs:` commit.
+ A short entry in `docs/ops/incidents/<file>`; the root-cause analysis is `/incident`'s, offered in the closing question.
+ 1. Show the draft, then "May I write `docs/ops/incidents/<file>`?" — one `AskUserQuestion`: write (Recommended) · show the draft first · not now.
+ 2. After the "write" answer: `touch .claude/.write-consent`, then `Write`.
+ 3. Record the commit gate: `Gate "/hotfix Phase 4: commit the postmortem note?"`.
+ 4. Ask, one `AskUserQuestion` (rule 7 (4), `git-workflow.md` § Documents): `docs: incident <slug>` staging exactly that file. HEAD is the hotfix branch, so name it and offer: switch to `<default>` and commit there (Recommended — a postmortem is a pipeline-wide document) · commit here (it rides the backport PR) · leave uncommitted. Nothing is committed without the answer; the fix never rides the `docs:` commit.
+ 5. On "switch": `git switch <default> && git pull --ff-only origin <default>`, commit, then `git switch hotfix/<slug>` back while the backport PR is open.
+ 6. Clear the gate: `<hooks>session-state.sh set Gate "—"`.
- Verdict: `FIXED` | `BLOCKED`. Next step — one `AskUserQuestion`: `/web-studio:incident` (copy mode `/incident`) for root-cause analysis (Recommended) · backport to the default branch (when step 3.7 was skipped) · stop here.
+ Verdict: `FIXED` | `BLOCKED` | `DONE (chore — PR open)`. Next step — one `AskUserQuestion`. Production: `/web-studio:incident` (copy mode `/incident`) for root-cause analysis (Recommended) · backport to `<default>` (when step 3.7 was skipped) · stop here. `--chore`: open the PR (Recommended) · stop here — no `/incident` after a chore.