Documentation:labelsync.specs.dev
Synchronise GitHub issue/PR labels across a set of repositories from a local YAML file.
labelsync is a reconciler, not a script: for each target repository it reads the current labels,
resolves the desired set, computes an ordered plan, and then applies it — or prints it, under
--dry-run. One labels.yml describes the labels you want; groups describe which repositories
should have them. Running it twice changes nothing the second time.
The command surface, then a real dry run against three public repositories — two renames each, one
drifted description, and everything else already in sync. It writes nothing, and the exit code
carries the 2 bit because it found drift.
# labels.ymlversion: 1groups:
ours:
org: yourorgexclude: ["*-archive"]defaults:
groups: [ours]renames:
- from: "bug"to: "type: bug"labels:
- name: "type: bug"color: "d73a4a"description: "Something isn't working"
- name: "type: feature"color: "0e8a16"description: "New functionality"labelsync groups # which repositories that selects, and why the rest were filtered out
labelsync sync --dry-run # the plan; writes nothing; exits 2 if anything has drifted
labelsync sync # apply itWhat it does, in one list:
- Creates, updates, and converges names, colours, descriptions, and casing across every
selected repository. Nothing is deleted unless you ask for
--mode=prune, which reports first and then asks which labels to remove. - Renames without losing anything. A
renames:entry becomes aPATCH, so every issue and pull request that carried the old label still carries it under the new name. - Never touches a repository no group selects. That is the safety property the rest is built on.
- Runs in CI.
--dry-runsets the2bit on drift, so a pull-request check fails when the committed config and the live labels disagree — test the bit, because a run that also skipped a repository exits6.--output=jsonemits NDJSON with a stableerror_kind.
brew install specsnl/tap/labelsyncOr go install github.com/specsnl/labelsync@latest, or download a tar.gz for your platform from
the releases page — Linux and macOS, amd64 and
arm64.
Building from a checkout needs nothing but Docker and Task:
task buildExport before you write a config. Descriptions in the config file are authoritative, so a config written from scratch clears every description your repositories already have:
labelsync export yourorg/yourrepo --out labels.ymlThe rest — describing the repositories, the dry run, the first apply — is in Getting started, and everything else — the configuration file, every command and flag, running in CI, and how it is built — is on the same site: labelsync.specs.dev.
Every command runs through Task, which wraps the Docker Compose services
that pin the Go, golangci-lint, Node, and Hugo versions — so a check runs the same way locally as it
does in CI. Run task --list for the full set.
task checkall # tidy:check, lint, test, md:check — run this before opening a pull requestConventions, workflow, and the house rules that reviews are held to: AGENTS.md.
MIT — see LICENSE.
