Setup GitHub Labels
Apply one intentional label vocabulary to whatever repo you run this in. The
tables below are the source of truth, not the GitHub UI. This is a prompt-driven
skill, not a deterministic script: read the set, explore the repo, confirm with
the user, then create the labels with
.
The canonical set
Three groups, kept small on purpose. This skill owns the GitHub label
objects
(name, color, description) for all of them.
owns the
agent wiring for the triage roles — the role→string mapping in
docs/agents/triage-labels.md
that tells the
skill which string to
apply — and never touches a label's color or description. So the two skills
split by concern, not by which labels: one owns the labels on GitHub, the other
owns how the agent refers to them. Anything a
tool already owns (Dependabot,
labeler actions) is
not here.
Issue axis
| Label | Color | Description (issue-scoped) |
|---|
| | issue: Reporting a defect to fix |
| | issue: Requesting a new capability or improvement |
| | issue: For other work: maintenance, refactor, docs |
/
/
mirror GitHub's Issue Types
(Bug / Feature / Task).
is the catch-all for refactor/docs/chore/test
work. If a repo ever moves under a GitHub org, these map 1:1 onto native Issue
Types and the labels can be retired.
Issue triage axis
The five whose-turn triage roles the
skill moves an issue through.
decides
which string each role maps to (and records
it in
docs/agents/triage-labels.md
); this skill creates the actual GitHub
labels with a consistent scope prefix, so it reads at a glance whether a state
applies to issues, PRs, or both.
| Label | Color | Description |
|---|
| | issue: Maintainer needs to evaluate this issue |
| | issue/pr: Waiting on reporter for more information |
| | issue: Fully specified, ready for an AFK agent |
| | issue: Requires human implementation |
| | issue: Will not be actioned |
is the one
label: a PR parked on an outside human reuses
it rather than minting a PR-scoped twin, so "waiting on a human" reads
identically on both. Every other triage role is issue-only.
PR-verdict axis
Whose-turn routing on a PR needs almost no labels: a non-draft PR with no
verdict is, by definition, the agent's to finalize, and a PR parked on an
outside human reuses the
triage label above rather than minting a
PR-scoped twin. What this axis adds is the agent's
terminal verdict once it
has finished finalizing a PR: the signal that tells the maintainer, at a glance,
what each open PR needs from them.
| Label | Color | Description (PR-scoped) |
|---|
| | pr: Agent finalized and endorses it: review and merge |
| | pr: Agent recommends closing: your call to review or close |
| | pr: Agent finalized it but the merge/close call is yours |
The verdict is three-way by design: the agent finalizes a PR (rebase, green
CI, polish) and then emits exactly one of the three. The split exists because
"not mergeable as-is" hides two states with very different maintainer effort:
- is the agent's active reject — broken, abandoned,
superseded, or clearly out of scope. The maintainer glances and closes.
- is for a PR whose code is sound but whose merge/close call
is a product or scope judgment the agent can't make — a clean, working feature
where the only open question is "does this project want it". The maintainer must
stop and decide; that decision is theirs, not the agent's.
Folding the second into
would be dishonest — the agent does not
recommend closing a sound feature — and would bury the most expensive pile (the
product calls only the maintainer can make) inside the cheap one (rubber-stamp
closes). So the maintainer's world is three filters: an emerald ship queue
(
is:pr is:open label:recommend-merge
), an amber decision queue
(
), and a red close pile (
).
is
not a cop-out hatch. It is only for "code is sound, the
call is product/scope". If the agent has a
technical reason the PR shouldn't
merge, it still owes a
with that reason in the review comment —
otherwise the decision queue swells with PRs the agent could have resolved and the
label loses its meaning.
The agent
never merges and never closes — both stay the maintainer's hand;
the verdict is a recommendation, not an action. That is why all three labels lead
with
rather than the ecosystem-conventional
: the
matched
/
/
set names them
honestly as recommendations, and — deliberately — steers clear of the label
strings merge bots watch (Kodiak,
Mergify, bors, GitHub auto-merge). A bot wired to merge on
would
turn the agent's recommendation into an actual merge and break this invariant, so
do
not rename it back to that conventional string.
A verdict endorses one specific diff, so it goes stale the moment the PR
changes: a new commit after a verdict means the agent must clear that label and
re-review before the emerald queue can be trusted again. Pushes after a verdict
are rare, so this stays a manual step rather than something worth a CI workflow.
A couple of label families are deliberately left out, because a tool already
owns them:
- Tool-managed labels ( from Dependabot, and other labels created
by labeler actions or bots) are owned by that tooling. Leave their color and
description alone; do not add them here or you will fight the tool that
recreates them.
- labels are per-repo (add them locally, ideally via path-based
), so they do not belong in a shared cross-repo set.
The "still being worked" state is owned by GitHub's native
draft flag, not a
label. A PR mid-iteration (including agent- or self-authored PRs, where the
author
is the maintainer so review-request doesn't apply) stays a draft until
the agent has a verdict. So there is deliberately no
/
label:
already
is the "your turn to ship"
signal, and the draft flag already owns "not yet". The maintainer's "needs my
attention" filter is
is:pr is:open label:recommend-merge
.
opens
autonomous PRs as draft for exactly this reason.
The same draft flag is why there is no
label: a "don't merge this
yet" state is almost always temporary (a PR kept open to exercise CI, or one
mid-iteration), and that is precisely what draft expresses. A draft PR already
cannot be merged, so a separate flag would just be a second, redundant mechanism
for "not yet".
PR
type and
area stay absent: PR type comes from the conventional-commit
title, and area is per-repo. Only the PR
verdict is shared here, because "is
this one mine to ship" generalizes across every repo with PRs. Each description
leads with a scope prefix —
,
, or
— to advertise where
it applies, so tools and people don't cross-apply.
Issue ↔ PR equivalents
Issues and PRs run the same underlying state machine —
whose turn is it, and
what must they do — but express it with different signals. This skill creates
both sides' labels;
owns the issue side's
vocabulary
(which string each triage role maps to). The PR side also leans on GitHub's
native draft flag. This table lines them up so a state reads the same whether
you're looking at an issue or a PR:
| State (whose turn / what's needed) | Issue (triage role) | PR (verdict + draft flag) |
|---|
| Unprocessed — someone must look | | no verdict label, non-draft |
| Blocked on an outside human | | (same label, reused) |
| Still being built / iterated | (open issue, no extra label) | draft flag |
| Agent's turn to act | | no verdict label, non-draft |
| Maintainer's turn — endorsed | (human implements) | (human reviews/merges) |
| Maintainer's turn — must decide | (maintainer evaluates) | (product/scope call) |
| Won't proceed | | |
Three asymmetries are intentional, not gaps:
- and collapse into one PR state. A non-draft
PR with no verdict already means "agent, finalize this", so the PR side never
separates "needs evaluation" from "agent's turn" — there is no PR
or label. On the issue side they stay distinct because an
issue can sit triaged-but-not-yet-assigned.
- The maintainer's terminal action differs. on an issue means
implement it; on a PR means review and merge it. Same "your
turn, human" role, different verb — which is why stays
issue-only and is its PR counterpart rather than a shared label.
- spans two PR states. On issues, "maintainer must evaluate" is
one state. On PRs it splits by when: a fresh PR is the agent's to finalize
(no verdict, non-draft), and only after the agent finalizes does a leftover
product/scope call earn its own verdict, . That is why
is a distinct PR label and the agent never puts
on a PR (that would invert it to "agent hasn't looked yet").
is absent on purpose: a "don't merge yet" PR is the
draft row
above, so it needs no label of its own.
Process
Detect, preview, confirm, then apply.
1. Detect the current repo
bash
gh repo view --json nameWithOwner -q .nameWithOwner
If that fails (no GitHub remote), ask the user which repo to target.
2. Preview (read-only)
Show the user which canonical labels are new vs already present on the repo, by
comparing the tables' labels against the repo's existing ones:
bash
gh label list --repo "$REPO" --limit 200 --json name -q '.[].name'
Labels in the tables but not in that list will be created; labels already
present will have their color/description updated.
3. Confirm, then apply
Creating labels on a (often public) repo is an outward-facing action, so confirm
with the user first. Then create each label from the tables with
, which
adds it if missing and updates color/description if it already exists:
bash
gh label create "type: bug" --color d73a4a --description "issue: Reporting a defect to fix" --force --repo "$REPO"
gh label create "type: feature" --color a2eeef --description "issue: Requesting a new capability or improvement" --force --repo "$REPO"
gh label create "type: task" --color cfd3d7 --description "issue: For other work: maintenance, refactor, docs" --force --repo "$REPO"
gh label create "needs-triage" --color fbca04 --description "issue: Maintainer needs to evaluate this issue" --force --repo "$REPO"
gh label create "needs-info" --color d876e3 --description "issue/pr: Waiting on reporter for more information" --force --repo "$REPO"
gh label create "ready-for-agent" --color 0e8a16 --description "issue: Fully specified, ready for an AFK agent" --force --repo "$REPO"
gh label create "ready-for-human" --color 1d76db --description "issue: Requires human implementation" --force --repo "$REPO"
gh label create "wontfix" --color ffffff --description "issue: Will not be actioned" --force --repo "$REPO"
gh label create "recommend-merge" --color 0E8A16 --description "pr: Agent finalized and endorses it: review and merge" --force --repo "$REPO"
gh label create "recommend-close" --color D93F0B --description "pr: Agent recommends closing: your call to review or close" --force --repo "$REPO"
gh label create "recommend-triage" --color FBCA04 --description "pr: Agent finalized it but the merge/close call is yours" --force --repo "$REPO"
To set up several repos, repeat with each
.
4. Report
Tell the user which labels were created vs updated. Non-canonical labels already
on the repo are
left untouched (this skill only adds/updates the canonical
set). If they want to retire a stray label, that is a manual, deliberate step:
gh label delete "<name>" --repo "$REPO" --yes
. Deleting strips it off every
issue/PR currently wearing it, so never do it without explicit confirmation.
This skill creates the triage labels, but the engineering skills only
apply
them if the repo also has the agent wiring — the role→string mapping in
docs/agents/triage-labels.md
. If that file is absent
(
test -f docs/agents/triage-labels.md
), recommend running
to write it. That's a one-line pointer, not a
requirement: the labels stand on their own; the wiring is what lets the
skill reach for them. Skip the recommendation when the file is already present.
Changing the set
Edit the tables above: add or remove a row, and keep the
lines
in step 3 in sync with them. For a triage role, also keep it consistent with the
role→string mapping
records in
docs/agents/triage-labels.md
— this skill owns the label's color and
description, that skill owns which string the role maps to. Keep the set small:
before adding a label, check it carries information the commit title, diff, or an
existing label (or another skill's labels) doesn't.