Synchronize labels from one repository (leader) to other repos.
The tool takes a source repository (the private giantswarm/giantswarm) and any number of target repositories (our public giantswarm/roadmap as well as customer repositories). Configured rules decide which labels are synchronized.
If the term "synchronization" raises high expectations: labels are actually copied in one direction only, from the leader repo to the target repo(s). If a label already exists in the target, it gets updated for color and description.
Take look at config.yaml for an example configuration file.
The rules part defines a list of rules, which get applied from first to last.
A rule applies if the label name fully matches the pattern (which is a Python regex). Partial matches are not enough.
If several rules apply, the last applying rule defines whether the label is included or ignored.
If a label (by its name) is not matched to any rule's pattern, it gets ignored (not synced).
docker build -t quay.io/giantswarm/github-label-sync .
docker run --rm -ti \
-v $HOME:/home/user \
quay.io/giantswarm/github-label-sync \
--token-path /home/user/.github-token
The tool will present you all actions that it would take, then you have to confirm that you want to proceed.
If used with --dry-run, no synchronization is happening and no confirmation is requested.
Use the --conf option to specify a configuration file path other than the default ./config.yaml.
Label sync runs automatically once a week (Mondays 06:00 UTC) via the
.github/workflows/label-sync.yaml GitHub Actions workflow, so target repos stay in sync
without anyone running the container by hand. It authenticates as the dedicated
giantswarm-label-sync GitHub App and runs cli.py --yes. The App is installed on all
repositories of the org with issues:write (labels are read and written through the Issues
API) and contents:read (only used to fetch data/customers.yaml from the leader).
You can also trigger it manually from the Actions tab ("Sync labels to customer repos" →
Run workflow), with a dry_run toggle to preview the plan without applying any changes.
--yes— apply the plan without the interactive confirmation prompt (for CI / cron runs).--max-jobs N— apply at mostNlabel operations per run (default 400,0= no limit) and defer the rest to the next run. The full plan is still printed, the deferred count shows up as a::notice::annotation, and the run stays green. GitHub allows roughly 500 content-creating API requests per hour and 80 per minute per App installation, and the installation token is only valid for one hour, so a larger plan (for example the first sync of a new customer repository, or the initial backlog) is spread over several runs. Writes are paced at a little under 80 per minute. To catch up faster than weekly, dispatch the workflow by hand with themax_jobsinput, leaving at least 75 minutes between the starts of two runs.GITHUB_TOKENenv var — read the token from the environment instead of the default~/.github-tokenfile, so unattended runs need not write the secret to disk. An explicit--token-pathalways takes precedence over the env var, so local use is unaffected by aGITHUB_TOKENexported for other tools (e.g. theghCLI).
A target or customer repository that cannot be read is logged and skipped, so the other
repositories are still synchronized. A repository that provably does not exist (for example a
stale entry in data/customers.yaml) only produces a warning (a ::warning:: annotation on the
Actions run) and the run stays green. "Provably" means: the run uses an App installation token
with access to all repositories of the organization, and the repository is not in the
installation's repository list. Every other read error, including a 404 that may also mean
"no access" (a personal token, or an installation limited to selected repositories), still ends
the run with exit code 1.
If a scheduled run fails, the workflow posts a warning with the run log link to
#team-planeteers (via the TEAM_PLANETEERS_SLACK_WEBHOOK_URL secret), so a broken Monday
run does not silently let label drift creep back in.