Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions runbooks/labels.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@

Use this runbook when cleaning or syncing labels across z-shell repositories.

## Sync scope

Canonical label sync targets **active, public, non-fork** repositories only.
`scripts/labels-sync.rb --all-repos` audits every repository the token can see,
so its raw output is wider than the sync scope. Filter before reading drift
totals, or private and fork repositories will keep reporting as regressions
when they are simply out of scope.

Excluded, and why:

- **Private repositories.** Not part of the public contributor-facing label
surface. Currently `z-shell/.github-private` and `z-shell/.trunk`.
- **Forks.** Their label sets largely belong to the upstream project, and
rewriting them loses that provenance without benefiting z-shell contributors.
- **Archived repositories.** Read-only by definition.

The exclusion is deliberate, not a backlog. Do not treat drift in these
repositories as a finding, and do not include them in org-wide totals without
saying which scope the number uses. If a repository leaves fork or private
status and becomes an active public repository, it enters scope at that point
and should be synced like any other.

## Source of truth

`lib/labels.yml` is the canonical organization label set.
Expand Down Expand Up @@ -116,6 +138,47 @@ Also retire spaced namespace variants such as `type: bug`, `area: docs`, `priori

Do not delete unknown labels in bulk. If a repository has a local label that is not obviously legacy, open or update an issue before removing it.

`labels-sync.rb` enforces this: `sync_policy.delete_unknown_labels` is `false`
and the script never deletes an unknown label. Leave that setting alone. An
approved sweep is a one-off operation, not a reason to make deletion the
default.

### Exception: an approved unknown-label sweep

The prohibition above is the default and stays the default. A bulk deletion is
permitted only as a separately approved operation that meets every condition
below. Missing any one of them means the sweep does not proceed.

1. **Explicit maintainer approval**, given against figures measured at approval
time rather than against an earlier report.
2. **Usage measured live from `repos/OWNER/REPO/issues?state=all`**, counting
items in every state. Never use the issue search API: its index lags bulk
label changes badly enough to report labels as in use months after they were
removed, and it fails in both directions.
3. **Delete only definitions attached to zero items.** A label carrying even one
item is migrated or left alone, never deleted.
4. **Preserve anything referenced by configuration**, verified by reading the
files rather than assuming. At minimum check `.github/labeler.yml` and the
`stale-*-label` and `exempt-*-labels` inputs of any stale or lock workflow.
Deleting a referenced label does not fail loudly; `actions/labeler` recreates
it on the next matching pull request, so the sweep quietly undoes itself.
5. **Archive every label definition** in the affected repositories, including
color and description, before the first write, so any deletion is
restorable.
6. **Pilot on one low-traffic repository** and verify it by hand before the
remaining repositories.
7. **Batch per repository with per-operation logging**, so a failure is
contained and attributable.
8. **Re-audit afterwards and re-derive the justification for every survivor**,
rather than trusting the plan that was executed.

Record the result on the owning issue, including the counts before and after
and the exclusion list actually applied.

The 2026-08-19 sweep is the reference execution: 1,483 unknown definitions
across the 86 in-scope repositories reduced to 93, with 1,390 deleted and every
survivor re-verified as in use or configuration-referenced.

## Label sync script

`scripts/labels-sync.rb` is the canonical entrypoint. The older `scripts/labels-dry-run.rb` name remains as a compatibility wrapper for existing local commands, but new runbook examples should use `scripts/labels-sync.rb`.
Expand Down
Loading