Add gerrit-workflow skill

Teaches AI agents how to push for review, update patch sets, handle
Change-Id trailers, work with dependent change stacks, and diagnose
common Gerrit push errors.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Change-Id: Ief2ee7fd6b5ff9e5e82beeede5fe6a92f4173b05
diff --git a/docs/claude-code.md b/docs/claude-code.md
index 4e07859..1287cb2 100644
--- a/docs/claude-code.md
+++ b/docs/claude-code.md
@@ -89,3 +89,20 @@
 the user to run it. See
 [Interactive Setup](configuration.md#interactive-setup-gerritsetup) in the
 configuration guide for details.
+
+## `gerrit-workflow` skill
+
+`skills/gerrit-workflow/SKILL.md` teaches the model how to drive the git side of
+Gerrit Code Review — pushing for review via `refs/for/<branch>`, creating new
+patch sets by amending without disturbing the `Change-Id` trailer, working with
+dependent change stacks and topics, and diagnosing common push errors such as
+`missing Change-Id in commit message footer` or `prohibited by Gerrit`.
+
+Unlike `/gerrit:setup`, this skill is model-invocable: Claude Code activates it
+automatically whenever a task involves Gerrit git operations, so you do not need
+to invoke it by name. Two reference files ship alongside it:
+
+- `references/gerrit-vs-github.md` — a side-by-side comparison of Gerrit and
+  GitHub/GitLab workflows for common tasks.
+- `references/change-id-details.md` — a deep dive on `Change-Id` creation,
+  squashing, cherry-picks, and edge cases.
diff --git a/skills/gerrit-workflow/SKILL.md b/skills/gerrit-workflow/SKILL.md
new file mode 100644
index 0000000..bcfe2e5
--- /dev/null
+++ b/skills/gerrit-workflow/SKILL.md
@@ -0,0 +1,247 @@
+---
+name: gerrit-workflow
+description: This skill should be used when the user asks to "submit a change to Gerrit", "push for review", "upload a patch", "update a change in Gerrit", "add a patch set", "work with Gerrit", "push to refs/for", "fix a missing Change-Id", "amend a Gerrit change", or any task involving git operations in a repository hosted on Gerrit Code Review. Also use when the user encounters Gerrit push errors such as "missing Change-Id in commit message footer" or "prohibited by Gerrit".
+allowed-tools: Bash, Read
+---
+
+# Gerrit Workflow
+
+Gerrit is a code review system that sits in front of a git repository. The key
+difference from GitHub/GitLab: **changes go through a review queue before
+landing in the branch**. This changes several git workflows in important ways.
+
+## Core Concepts
+
+### Changes and Patch Sets
+
+A **Change** is the unit of review in Gerrit — it corresponds to one logical
+commit. Each change has:
+
+- A numeric **Change number** (e.g., `12345`)
+- A **Change-Id** trailer in the commit message (e.g.,
+  `Change-Id: Ic8aaa0728a43936cd4c6e1ed590e01ba8f0fbf5b`)
+
+When a commit is revised and re-uploaded, the new version becomes a new **patch
+set** on the same change. Gerrit associates them via the `Change-Id` trailer —
+this is what makes the `Change-Id` essential.
+
+### The Magic `refs/for/<branch>` Ref
+
+Gerrit intercepts pushes to `refs/for/<branch>`. These pushes never actually
+create that ref; instead Gerrit creates or updates a Change for review. To land
+code in `master`:
+
+```
+# NOT this (bypasses review):
+git push origin HEAD:refs/heads/master
+
+# This — submits for review:
+git push origin HEAD:refs/for/master
+```
+
+### The commit-msg Hook
+
+Gerrit provides a `commit-msg` hook that auto-inserts `Change-Id` trailers.
+Without it, the `Change-Id` must be added manually. To install:
+
+```bash
+# Via curl (HTTP):
+curl -Lo .git/hooks/commit-msg <gerrit-url>/tools/hooks/commit-msg
+chmod u+x .git/hooks/commit-msg
+
+# Via scp (SSH):
+scp -p -P 29418 <user>@<gerrit-host>:hooks/commit-msg .git/hooks/
+chmod u+x .git/hooks/commit-msg
+```
+
+Check if it is already installed:
+
+```bash
+ls -la .git/hooks/commit-msg
+```
+
+## Common Workflows
+
+### 1. Create a New Change
+
+```bash
+# Make changes, then commit (hook adds Change-Id automatically)
+git add <files>
+git commit -m "Fix the foo widget"
+
+# Verify Change-Id was added:
+git log -1
+
+# Push for review against a target branch:
+git push origin HEAD:refs/for/master
+```
+
+The server prints the URL of the new change.
+
+### 2. Update an Existing Change (New Patch Set)
+
+After receiving review feedback, amend the commit. **Do not modify or remove the
+`Change-Id` line** — Gerrit uses it to associate the new commit with the
+existing change.
+
+```bash
+# Edit files, then:
+git add <files>
+git commit --amend   # keep Change-Id unchanged in the editor
+
+# Push again — Gerrit creates patch set 2 on the same change:
+git push origin HEAD:refs/for/master
+```
+
+### 3. Work on Multiple Independent Changes
+
+Each change should be its own commit on its own local branch:
+
+```bash
+git checkout -b fix-foo origin/master
+# make change A, commit, push for review
+
+git checkout -b fix-bar origin/master
+# make change B, commit, push for review
+```
+
+Do not stack unrelated changes on a single branch unless they are intentionally
+dependent (see below).
+
+### 4. Dependent / Stacked Changes
+
+Gerrit supports chains of dependent changes. Push multiple commits in one push;
+Gerrit creates one change per commit, linked in a relation chain:
+
+```bash
+git checkout -b feature origin/master
+git commit -m "Step 1: add plumbing"       # Change-Id: I111...
+git commit -m "Step 2: wire up the UI"     # Change-Id: I222...
+git push origin HEAD:refs/for/master       # creates two linked changes
+```
+
+Each change can be reviewed and submitted independently, but Gerrit shows the
+dependency chain.
+
+### 5. Rebase and Re-push
+
+When the target branch has moved on, rebase and re-push. Keep Change-Ids intact:
+
+```bash
+git fetch origin
+git rebase origin/master   # Change-Ids are preserved automatically
+git push origin HEAD:refs/for/master
+```
+
+### 6. Squashing Commits Before Push
+
+When squashing several commits, keep exactly **one** `Change-Id` — prefer the
+one already known to Gerrit (i.e., the one that was previously pushed, if any).
+Remove the others:
+
+```bash
+git rebase -i origin/master   # squash commits
+# In the resulting commit message, delete all but one Change-Id line
+git push origin HEAD:refs/for/master
+```
+
+### 7. Cherry-pick to Another Branch
+
+To propose the same fix on a maintenance branch, cherry-pick and **generate a
+new Change-Id** (delete the old one so the hook creates a fresh one):
+
+```bash
+git checkout -b backport-3.10 origin/stable-3.10
+git cherry-pick <commit-sha>
+git commit --amend   # delete the Change-Id line; save
+# hook regenerates a new Change-Id
+git push origin HEAD:refs/for/stable-3.10
+```
+
+Or, keep the original Change-Id to have Gerrit treat it as a replacement for the
+same change on that branch.
+
+## Push Options
+
+Additional metadata can be sent with the push via `%` options or `-o`:
+
+```bash
+# Add a topic (groups related changes):
+git push origin HEAD:refs/for/master%topic=my-feature
+
+# Request specific reviewers:
+git push origin HEAD:refs/for/master%r=alice@example.com,cc=bob@example.com
+
+# Mark as work-in-progress:
+git push origin HEAD:refs/for/master%wip
+
+# Mark ready for review:
+git push origin HEAD:refs/for/master%ready
+
+# Apply a review label on push:
+git push origin HEAD:refs/for/master%l=Verified+1
+
+# Suppress notifications:
+git push origin HEAD:refs/for/master%notify=NONE
+
+# Combine options with commas:
+git push origin HEAD:refs/for/master%topic=my-feature,r=alice@example.com
+```
+
+## Fetching Changes
+
+To check out an existing change locally (e.g., to test it):
+
+```bash
+# The change page shows a "Download" command; it looks like:
+git fetch origin refs/changes/45/12345/3 && git checkout FETCH_HEAD
+
+# Where: refs/changes/<last-two-digits-of-change-num>/<change-num>/<patch-set>
+```
+
+## Common Errors and Fixes
+
+### `missing Change-Id in commit message footer`
+
+The commit has no `Change-Id`. Fix:
+
+```bash
+# If hook is installed, amend will add it:
+git commit --amend --no-edit
+
+# If hook is NOT installed, add it manually:
+# Copy the Change-Id from the Gerrit web UI change page, then:
+git commit --amend   # paste "Change-Id: I..." into the footer
+```
+
+### `! [remote rejected] ... (prohibited by Gerrit)`
+
+Direct push to `refs/heads/*` was rejected. Push to `refs/for/<branch>` instead.
+
+### `Change xxx: patch set already exists`
+
+The commit hash hasn't changed since the last push (no new patch set was made).
+Amend the commit to create a distinct object, then push again.
+
+### `! [remote rejected] ... (no new changes)`
+
+All commits being pushed are already known to Gerrit. This often means the
+branch is already up to date with the target.
+
+## Critical Rules
+
+- **Never push directly to `refs/heads/*`** unless intentionally bypassing
+  review (requires special permissions).
+- **Never change or remove the `Change-Id` trailer** when amending a commit that
+  is already under review — doing so creates a second, orphaned change.
+- **One logical change = one commit.** Gerrit reviews commits, not branches.
+- **Always verify `Change-Id` is present** with `git log -1` before pushing.
+- **Keep Change-Ids when rebasing** — git preserves them through rebase
+  automatically.
+
+## Additional Resources
+
+- **`references/gerrit-vs-github.md`** — Side-by-side comparison of Gerrit and
+  GitHub/GitLab workflows for common tasks
+- **`references/change-id-details.md`** — Deep dive on Change-Id: creation,
+  squashing, cherry-picks, and edge cases
diff --git a/skills/gerrit-workflow/references/change-id-details.md b/skills/gerrit-workflow/references/change-id-details.md
new file mode 100644
index 0000000..bd1b59b
--- /dev/null
+++ b/skills/gerrit-workflow/references/change-id-details.md
@@ -0,0 +1,176 @@
+# Change-Id: Deep Dive
+
+## What Is a Change-Id?
+
+A `Change-Id` is a SHA-1-like identifier prefixed with `I` (uppercase i), placed
+as a git trailer in the commit message footer. Example:
+
+```
+Fix the foo widget
+
+We want a bar, because it improves the foo by providing more
+wizbangery to the dowhatimeanery.
+
+Bug: #42
+Change-Id: Ic8aaa0728a43936cd4c6e1ed590e01ba8f0fbf5b
+Signed-off-by: A. U. Thor <author@example.com>
+```
+
+The `Change-Id` must be in the **last paragraph** of the commit message (the
+"footer"). It can coexist with `Signed-off-by`, `Acked-by`, `Bug:`, `Fixes:`,
+etc.
+
+## How the commit-msg Hook Creates Change-Ids
+
+The hook creates a virtual git commit object from the tree SHA, parent SHA,
+author info, committer timestamp, and the proposed message. The SHA-1 of that
+virtual object becomes the `Change-Id`. Because the committer timestamp and
+parent SHA are included, the same message on two different commits produces two
+different Change-Ids.
+
+The hook **does nothing** if a `Change-Id` line is already present in the
+footer.
+
+## When to Keep the Same Change-Id
+
+Keep the existing `Change-Id` unchanged when:
+
+- **Amending a commit** to address review feedback — this adds a new patch set
+  to the existing change.
+- **Rebasing a commit** — git preserves trailers through rebase automatically.
+- **Cherry-picking to the same branch** with the intention of updating the same
+  review.
+- **Updating the commit message** for any reason (spelling fix, adding a
+  `Signed-off-by`, etc.).
+- **Backporting** to a different branch — the backport is a separate change on a
+  separate branch, but logically has the same commit "intent". See
+  [Uniqueness](#uniqueness-and-scope) below.
+
+## When to Generate a New Change-Id
+
+Delete the `Change-Id` line (or the entire trailer) and let the hook regenerate
+a fresh one when:
+
+- **Starting a completely new change** from an existing commit that happened to
+  have a Change-Id.
+- You want to split one change into two separate reviews.
+
+To force a new `Change-Id` on an existing commit:
+
+```bash
+git commit --amend   # remove the Change-Id line, save
+# hook runs and inserts a new Change-Id
+git log -1           # verify the new Change-Id
+```
+
+## Squashing Commits
+
+When squashing two or more commits that each have a `Change-Id`, keep exactly
+**one**:
+
+1. Prefer the one already uploaded to Gerrit (someone may have left comments on
+   it).
+2. If both were uploaded, ask the user if they have a preference or keep the one
+   with existing comments on it — then abandon the orphaned change in the Gerrit
+   web UI.
+3. If neither was uploaded, keep any one and delete the rest.
+
+Interactive rebase example:
+
+```bash
+git rebase -i origin/master
+# Mark commits as "squash" or "fixup"
+# In the resulting commit message editor:
+#   Keep ONE Change-Id line, delete the others
+```
+
+## The Link Footer Alternative
+
+Some projects (e.g., the Linux kernel) use a `Link` footer instead of
+`Change-Id`:
+
+```
+Link: https://gerrit-review.googlesource.com/id/Ic8aaa0728a43936cd4c6e1ed590e01ba8f0fbf5b
+```
+
+The hook generates this style when `gerrit.reviewUrl` is set in the git config:
+
+```bash
+git config gerrit.reviewUrl https://gerrit-review.googlesource.com/
+```
+
+Gerrit recognizes both styles. The base URL in the `Link` footer must match the
+server's configured base URL.
+
+## Disabling Change-Id Generation
+
+To prevent the hook from adding a `Change-Id` (e.g., for fixup or squash
+commits):
+
+```bash
+git config gerrit.createChangeId false
+```
+
+Or, create a commit whose subject begins with a lowercase word followed by `!`
+(e.g., `nopush!`).
+
+To force insertion even for such commits:
+
+```bash
+git config gerrit.createChangeId always
+```
+
+## Diagnosing Change-Id Problems
+
+### Missing Change-Id push rejection
+
+When Gerrit is configured to require a Change-Id (the default), pushing without
+one produces:
+
+```
+! [remote rejected] HEAD -> refs/for/master (missing Change-Id in commit message footer)
+```
+
+Fix: amend the commit to add a `Change-Id` line in the footer, or install the
+`commit-msg` hook so it is added automatically on future commits.
+
+Note: some repositories are configured with
+`Require Change-Id in commit message = FALSE`, which suppresses this error.
+Behavior varies per repo.
+
+### Auto-generated Change-Id fallback
+
+If you push a commit without a Change-Id and Gerrit accepts it (repo opt-out),
+Gerrit auto-generates one and displays it in the web UI. This ID is **not** in
+the commit message, so a subsequent amended push will not be associated with
+that change unless you manually copy the displayed `Change-Id` line into the
+commit message footer before pushing again.
+
+Check whether a commit has a `Change-Id`:
+
+```bash
+git log -1 --format='%B' | grep '^Change-Id:'
+```
+
+Check if the Change-Id is in the footer (last paragraph), not the body:
+
+```bash
+# The footer is separated from the body by a blank line.
+# If Change-Id appears before the last blank-line-delimited paragraph, Gerrit rejects it.
+# Use the `%(trailers)` format to leverage `git interpret-trailers` for parsing.
+git log -1 --format='%(trailers:key=Change-Id)'
+```
+
+Fix a `Change-Id` that landed in the wrong place:
+
+```bash
+git commit --amend
+# Move the Change-Id line to the very end of the message (after a blank line)
+```
+
+## Uniqueness and Scope
+
+A `Change-Id` is unique per (repository, branch) combination. The same
+`Change-Id` **can** appear on different branches (e.g., when a commit is
+cherry-picked to a maintenance branch). Gerrit distinguishes them by repository
+and branch, so there is no conflict.
diff --git a/skills/gerrit-workflow/references/gerrit-vs-github.md b/skills/gerrit-workflow/references/gerrit-vs-github.md
new file mode 100644
index 0000000..409d17c
--- /dev/null
+++ b/skills/gerrit-workflow/references/gerrit-vs-github.md
@@ -0,0 +1,95 @@
+# Gerrit vs GitHub/GitLab: Workflow Comparison
+
+This reference maps common GitHub/GitLab operations to their Gerrit equivalents.
+
+## Creating a Change for Review
+
+| GitHub/GitLab               | Gerrit                                                                                               |
+| --------------------------- | ---------------------------------------------------------------------------------------------------- |
+| Push a branch, open a PR/MR | Push commits to `refs/for/<branch>`                                                                  |
+| `git push origin my-branch` | `git push origin HEAD:refs/for/master`                                                               |
+| Open PR in the web UI       | Gerrit change is created automatically on push to `refs/for/<branch>`                                |
+| Branch stays in the remote  | No branch under `refs/heads/` is created; the change has per-patchset branches under `refs/changes/` |
+
+## Updating a Change
+
+| GitHub/GitLab                      | Gerrit                                                        |
+| ---------------------------------- | ------------------------------------------------------------- |
+| Push more commits to the PR branch | Amend the commit, push to `refs/for/<branch>` again           |
+| Force-push the branch              | `git commit --amend` + `git push origin HEAD:refs/for/master` |
+| PR is updated with new commits     | Gerrit adds a new patch set to the same change                |
+
+**Critical difference:** GitHub tracks changes by branch name; Gerrit tracks
+them by `Change-Id` trailer in the commit message. Changing the `Change-Id`
+creates an entirely new change.
+
+## Merging / Submitting
+
+| GitHub/GitLab                        | Gerrit                                                                   |
+| ------------------------------------ | ------------------------------------------------------------------------ |
+| Merge button in the web UI           | Submit button in the Gerrit web UI (after approvals)                     |
+| Merge commit or squash merge         | Configured per-project (fast-forward, merge, rebase, cherry-pick)        |
+| PR branch can be deleted after merge | No branch to delete — the commit lands directly in `refs/heads/<branch>` |
+
+## Reviewing Others' Work
+
+| GitHub/GitLab                  | Gerrit                                                                                                          |
+| ------------------------------ | --------------------------------------------------------------------------------------------------------------- |
+| Fetch the PR branch            | Fetch the change ref `refs/changes/NN/changeNum/patchSetNum` (where `NN` is the last two digits of `changeNum`) |
+| Comment on lines in the UI     | Same in Gerrit's web UI                                                                                         |
+| "Approve" or "Request changes" | Vote on labels (e.g., `Code-Review +2` to approve, `-2` to block)                                               |
+
+## Fetching the Latest Branch
+
+```bash
+# Same in both (assuming remote name is `origin`):
+git fetch origin
+git rebase origin/master
+```
+
+## Working With Multiple Commits (a Change series)
+
+**GitHub/GitLab:** A PR can have many commits; reviewers see all of them.
+
+**Gerrit:** Each commit is its own change. For a feature that spans multiple
+commits:
+
+- Each commit gets its own change number and is reviewed independently.
+- Each commit should be reviewable standalone (self-contained).
+- The relation chain is shown in the Gerrit UI.
+- Changes can be submitted once their parent change (if any) is submittable or
+  can be submitted together via topics (if the Gerrit server is configured for
+  topic submit).
+
+## Topics and Hashtags (Grouping Related Changes)
+
+Gerrit topics group related changes across one or more repositories:
+
+```bash
+git push origin HEAD:refs/for/master%topic=my-feature-name
+```
+
+Changes with the same topic appear together in search results and (if the server
+configuration is enabled) the "Submitted Together" section of the review screen.
+
+Hashtags are similar, but you can have multiple hashtags on a single change and
+they don't impact submit behavior in any way. Hashtags should be preferred
+unless the submit grouping behavior is enabled and needed.
+
+## No Pull, No Merge Commits From Branches
+
+In a Gerrit workflow:
+
+- **Don't** create merge commits to incorporate remote changes — rebase instead.
+- **Don't** create long-lived feature branches — each change is a single commit.
+- **Don't** `git pull` (which merges) — use `git fetch` + `git rebase`.
+
+```bash
+# Update your local work on top of origin:
+git fetch origin
+git rebase origin/master
+
+# If you have a stacked series:
+git rebase origin/master  # rebases the whole stack; Change-Ids are preserved
+git push origin HEAD:refs/for/master  # updates all changes in the stack
+```